import ReactReconciler from "react-reconciler";
import { DefaultEventPriority } from "react-reconciler/constants";
import { generateUUIDv4 } from "../crypto/generate-uuidv4";
const globalObj =
typeof globalThis !== "undefined"
? globalThis
: typeof window !== "undefined"
? window
: typeof global !== "undefined"
? global
: this || new Function("return this")();
// setTimeout / clearTimeout 폴리필 주입 (넥사크로 런타임 환경 지원용)
if (typeof globalObj.setTimeout === "undefined") {
var __timerIdSeq = 0;
var __timers = {};
globalObj.setTimeout = function (callback, delay) {
var id = ++__timerIdSeq;
__timers[id] = true;
Promise.resolve().then(function () {
if (__timers[id]) {
delete __timers[id];
callback();
}
});
return id;
};
globalObj.clearTimeout = function (id) {
delete __timers[id];
};
}
function applyLayoutProps(comp, lp) {
if (!comp || !lp) return;
// 1. 값 설정 (유효한 값 우선 대입하여 가로/세로 2개 좌표 원칙을 만족시킴)
if (lp.top !== undefined && lp.top !== null && typeof comp.set_top === "function") {
comp.set_top(lp.top);
}
if (lp.bottom !== undefined && lp.bottom !== null && typeof comp.set_bottom === "function") {
comp.set_bottom(lp.bottom);
}
if (lp.left !== undefined && lp.left !== null && typeof comp.set_left === "function") {
comp.set_left(lp.left);
}
if (lp.right !== undefined && lp.right !== null && typeof comp.set_right === "function") {
comp.set_right(lp.right);
}
// 2. 값 제거 (반대편 및 지정된 null 좌표 해제)
if ((lp.top === null || lp.top === undefined) && lp.bottom !== undefined && lp.bottom !== null && typeof comp.set_top === "function") {
comp.set_top(null);
}
if ((lp.bottom === null || lp.bottom === undefined) && lp.top !== undefined && lp.top !== null && typeof comp.set_bottom === "function") {
comp.set_bottom(null);
}
if ((lp.left === null || lp.left === undefined) && lp.right !== undefined && lp.right !== null && typeof comp.set_left === "function") {
comp.set_left(null);
}
if ((lp.right === null || lp.right === undefined) && lp.left !== undefined && lp.left !== null && typeof comp.set_right === "function") {
comp.set_right(null);
}
}
// 렌더링 틱(Commit) 종료 후 일괄적으로 스크롤을 초기화하기 위해 모아두는 큐
const pendingScrollForms = new Set();
// 일괄 렌더링(Commit) 중 리드로우를 중지할 폼들을 기억하는 큐
const pendingRedrawForms = new Set();
const pendingLayoutRestores = new Set();
const pendingLayoutRestoreComps = new Set();
const pendingOrderRestoreParents = new Set();
/**
* @typedef {Object} NexacroInstanceResult
* @property {nexacro.Component} comp 생성된 컴포넌트
* @property {nexacro.Form} form 자식을 추가할 form.
*/
/**
* 넥사크로 컴포넌트 타입 → 생성 함수 매핑
*
* @param {string} type
* 컴포넌트 타입 문자열
* @param {Object} props
* 프로퍼티
* @param {nexacro.Form | nexacro.Div} parentContainer
* 부모 컨테이너
* @returns {NexacroInstanceResult}
* 생성된 컴포넌트와 자식을 추가할 form.
* @access private
*/
function createNexacroInstance(type, props, parentContainer) {
const id = props.id ?? "v" + generateUUIDv4().replace(/-/g, "");
const { left, top, width, height, right, bottom } = props;
let comp;
switch (type) {
case "nx-div":
comp = new nexacro.Div(id, left, top, width, height, right, bottom);
break;
case "nx-static":
comp = new nexacro.Static(
id,
left,
top,
width,
height,
right,
bottom,
);
break;
case "nx-button":
comp = new nexacro.Button(
id,
left,
top,
width,
height,
right,
bottom,
);
break;
case "nx-edit":
comp = new nexacro.Edit(
id,
left,
top,
width,
height,
right,
bottom,
);
break;
case "nx-textarea":
comp = new nexacro.TextArea(
id,
left,
top,
width,
height,
right,
bottom,
);
break;
case "nx-checkbox":
comp = new nexacro.CheckBox(
id,
left,
top,
width,
height,
right,
bottom,
);
break;
case "nx-combo":
comp = new nexacro.Combo(
id,
left,
top,
width,
height,
right,
bottom,
);
break;
case "nx-calendar":
comp = new nexacro.Calendar(
id,
left,
top,
width,
height,
right,
bottom,
);
break;
case "nx-spin":
comp = new nexacro.Spin(
id,
left,
top,
width,
height,
right,
bottom,
);
break;
case "nx-grid":
comp = new nexacro.Grid(
id,
left,
top,
width,
height,
right,
bottom,
);
break;
case "nx-tab":
comp = new nexacro.Tab(id, left, top, width, height, right, bottom);
break;
case "nx-image":
comp = new nexacro.Image(
id,
left,
top,
width,
height,
right,
bottom,
);
break;
case "nx-progressbar":
comp = new nexacro.ProgressBar(
id,
left,
top,
width,
height,
right,
bottom,
);
break;
case "nx-listbox":
comp = new nexacro.ListBox(
id,
left,
top,
width,
height,
right,
bottom,
);
break;
case "nx-panel":
comp = new nexacro.Panel(
id,
left,
top,
width,
height,
right,
bottom,
);
break;
default:
throw new Error(
`[react-nexacro] 알 수 없는 컴포넌트 타입: "${type}"`,
);
}
return comp;
}
/**
* 넥사크로 컴포넌트에 props를 적용한다.
* 이벤트 핸들러(on*)와 일반 프로퍼티를 구분하여 처리한다.
*
* @param {nexacro.Component} comp
* @param {Object} props
* @access private
*/
function wrapWithBatchedUpdates(fn) {
return function () {
var args = arguments;
var self = this;
NexacroReconciler.batchedUpdates(function () {
fn.apply(self, args);
});
};
}
function applyProps(comp, props) {
const SKIP_KEYS = new Set(["id", "children"]);
// 이벤트 핸들러 매핑 (React prop → 넥사크로 이벤트명)
const EVENT_MAP = {
onClick: "onclick",
onDblClick: "ondblclick",
onChanged: "onchanged",
onCanChange: "canchange",
onDropDown: "ondropdown",
onCloseUp: "oncloseup",
onItemChanged: "onitemchanged",
onKeyDown: "onkeydown",
onKeyUp: "onkeyup",
onSetFocus: "onsetfocus",
onKillFocus: "onkillfocus",
onMouseOver: "onmouseover",
onMouseOut: "onmouseout",
onHScroll: "onhscroll",
onVScroll: "onvscroll",
onLoad: "onload",
onMove: "onmove",
onSize: "onsize",
};
// 래핑된 핸들러 저장소 초기화
if (!comp._nxWrappedHandlers) comp._nxWrappedHandlers = {};
if (!comp.__reactLayoutProps) comp.__reactLayoutProps = {};
if (!comp.__reactLayoutProps) comp.__reactLayoutProps = {};
Object.keys(props).forEach((key) => {
if (SKIP_KEYS.has(key)) return;
const val = props[key];
if (EVENT_MAP[key]) {
if (typeof val === "function") {
const wrapped = wrapWithBatchedUpdates(val);
comp._nxWrappedHandlers[key] = wrapped;
comp.addEventHandler(EVENT_MAP[key], wrapped);
}
return;
}
// ref 처리
if (key === "ref") {
if (val && typeof val === "object" && "current" in val) {
val.current = comp;
}
return;
}
// 일반 프로퍼티 적용
try {
const LAYOUT_KEYS = new Set(["top", "left", "right", "bottom"]);
if (LAYOUT_KEYS.has(key)) {
comp.__reactLayoutProps[key] = val;
return;
}
if (key === "layouttype") {
if (typeof comp.set_type === "function") comp.set_type(val);
else comp["type"] = val;
} else if (key === "_forceLayout") {
// 무시
} else {
const setter = "set_" + key;
if (typeof comp[setter] === "function") {
comp[setter](val);
} else {
comp[key] = val;
}
}
} catch (e) {
// 읽기 전용 프로퍼티 등 무시
}
});
applyLayoutProps(comp, comp.__reactLayoutProps);
}
/**
* 이전 props와 새 props를 비교하여 변경된 항목만 업데이트한다.
*
* @param {nexacro.Component} comp
* @param {Object} oldProps
* @param {Object} newProps
* @access private
*/
function updateProps(comp, oldProps, newProps) {
const EVENT_MAP = {
onClick: "onclick",
onDblClick: "ondblclick",
onChanged: "onchanged",
onCanChange: "canchange",
onDropDown: "ondropdown",
onCloseUp: "oncloseup",
onItemChanged: "onitemchanged",
onKeyDown: "onkeydown",
onKeyUp: "onkeyup",
onSetFocus: "onsetfocus",
onKillFocus: "onkillfocus",
onMouseOver: "onmouseover",
onMouseOut: "onmouseout",
onHScroll: "onhscroll",
onVScroll: "onvscroll",
onLoad: "onload",
onMove: "onmove",
onSize: "onsize",
};
const SKIP_KEYS = new Set(["id", "children"]);
// 래핑된 핸들러 저장소 초기화
if (!comp._nxWrappedHandlers) comp._nxWrappedHandlers = {};
if (!comp.__reactLayoutProps) comp.__reactLayoutProps = {};
// 이전 이벤트 핸들러 제거 (저장된 래핑 함수로 정확히 제거)
Object.keys(oldProps).forEach((key) => {
if (SKIP_KEYS.has(key)) return;
if (EVENT_MAP[key] && typeof oldProps[key] === "function") {
try {
const prevWrapped = comp._nxWrappedHandlers[key];
if (prevWrapped) {
comp.removeEventHandler(EVENT_MAP[key], prevWrapped);
delete comp._nxWrappedHandlers[key];
}
} catch (e) {}
}
});
// 새 props 적용
Object.keys(newProps).forEach((key) => {
if (SKIP_KEYS.has(key)) return;
if (key === "ref") return;
const val = newProps[key];
const LAYOUT_KEYS = new Set(["top", "left", "right", "bottom"]);
if (EVENT_MAP[key]) {
if (typeof val === "function") {
const wrapped = wrapWithBatchedUpdates(val);
comp._nxWrappedHandlers[key] = wrapped;
comp.addEventHandler(EVENT_MAP[key], wrapped);
}
return;
}
if (key === "_forceLayout") {
if (oldProps[key] !== val) {
pendingLayoutRestoreComps.add(comp);
}
return;
}
if (oldProps[key] !== val) {
try {
if (LAYOUT_KEYS.has(key)) {
comp.__reactLayoutProps[key] = val;
pendingLayoutRestoreComps.add(comp);
return;
}
if (key === "layouttype") {
if (typeof comp.set_type === "function") comp.set_type(val);
else comp["type"] = val;
} else {
const setter = "set_" + key;
if (typeof comp[setter] === "function") {
comp[setter](val);
} else {
comp[key] = val;
}
}
} catch (e) {
}
}
});
applyLayoutProps(comp, comp.__reactLayoutProps);
}
/**
* 컨테이너(Div 또는 Form)에서 자식을 추가할 수 있는 form을 반환한다.
*
* @param {nexacro.Div | nexacro.Form} container
* @returns {nexacro.Form}
* @access private
*/
function getForm(container) {
if (container instanceof nexacro.Div) {
return container.form;
}
if (container instanceof nexacro.Form) {
return container;
}
// Tabpage 등
if (container && container.form) {
return container.form;
}
return container;
}
/**
* 변경이 발생한 컴포넌트부터 최상위 폼까지, 그리고 하위 자손 폼들까지 모두 스크롤 초기화 대기열에 추가한다.
*
* @param {*} startNode 스크롤 갱신을 시작할 자식 컴포넌트 또는 부모 컨테이너
* @access private
*/
function markScrollForAncestors(startNode) {
// startNode 자체의 form (자신이 Div 등 컨테이너일 경우)
if (startNode) {
try {
const selfForm = getForm(startNode);
if (selfForm && typeof selfForm.resetScroll === "function") {
pendingScrollForms.add(selfForm);
}
} catch(e) {}
}
// 상위(Ancestors) 탐색만 수행하여 O(1) ~ O(Depth) 수준으로 최적화 (하위 탐색 제거)
let current = startNode;
while (current) {
const form = getForm(current.parent || current);
if (form && typeof form.resetScroll === "function") {
pendingScrollForms.add(form);
}
current = current.parent;
}
}
/**
* Panel 여부를 반환한다.
*
* @param {*} instance
* @returns {boolean}
* @access private
*/
function recordChildInitial(parent, child) {
if (!parent) return;
if (!parent._reactChildren) parent._reactChildren = [];
const idx = parent._reactChildren.indexOf(child);
if (idx === -1) {
parent._reactChildren.push(child);
}
}
function recordChildAppend(parent, child) {
if (!parent) return;
if (!parent._reactChildren) parent._reactChildren = [];
const idx = parent._reactChildren.indexOf(child);
if (idx > -1) {
parent._reactChildren.splice(idx, 1);
}
parent._reactChildren.push(child);
pendingOrderRestoreParents.add(parent);
}
function recordChildInsertBefore(parent, child, beforeChild) {
if (!parent) return;
if (!parent._reactChildren) parent._reactChildren = [];
const idx = parent._reactChildren.indexOf(child);
if (idx > -1) {
parent._reactChildren.splice(idx, 1);
}
const beforeIdx = parent._reactChildren.indexOf(beforeChild);
if (beforeIdx > -1) {
parent._reactChildren.splice(beforeIdx, 0, child);
} else {
parent._reactChildren.push(child);
}
pendingOrderRestoreParents.add(parent);
}
function recordChildRemove(parent, child) {
if (!parent || !parent._reactChildren) return;
const idx = parent._reactChildren.indexOf(child);
if (idx > -1) {
parent._reactChildren.splice(idx, 1);
}
pendingOrderRestoreParents.add(parent);
}
function isPanel(instance) {
return instance instanceof nexacro.Panel;
}
/**
* 부모 컨테이너에 자식을 추가한다.
* - Panel: 부모 form에 addChild 후 panel.addItem(id, id) 로 등록
* - Div/Form: getForm(parent).addChild.
*
* @param {*} parentInstance 부모 넥사크로 컴포넌트 또는 Form.
* @param {*} child 자식 넥사크로 컴포넌트
* @access private
*/
function fixComponentLayoutChains(comp, form) {
if (!form || !form.components || !comp) return;
const targetPrefix = comp.id + ":";
const layoutKeys = ["top", "left", "right", "bottom"];
Object.values(form.components).forEach(sibling => {
if (sibling === comp) return;
layoutKeys.forEach(key => {
try {
const lpVal = sibling.__reactLayoutProps && sibling.__reactLayoutProps[key];
const rawVal = sibling[key];
const matchLp = typeof lpVal === 'string' && lpVal.startsWith(targetPrefix);
const matchRaw = typeof rawVal === 'string' && rawVal.startsWith(targetPrefix);
if (matchLp || matchRaw) {
const saved = matchLp ? lpVal : rawVal;
const setter = "set_" + key;
if (typeof sibling[setter] === "function") {
sibling[setter](null);
sibling[setter](saved);
}
}
} catch (e) {}
});
});
}
function relinkDependents(newComp, form) {
if (!form || !form.components) return;
try {
fixComponentLayoutChains(newComp, form);
} catch(e) {}
}
function flushPendingChildren(comp, parentForm) {
if (isPanel(comp) && comp._pendingChildren) {
const children = comp._pendingChildren;
comp._pendingChildren = null;
children.forEach((child) => {
if (parentForm && typeof parentForm.addChild === "function") {
try {
parentForm.addChild(child.id, child);
child.show?.();
if (child.__reactLayoutProps) {
pendingLayoutRestoreComps.add(child);
}
comp.addItem(child.id, child.id);
markScrollForAncestors(comp);
relinkDependents(child, parentForm);
} catch (e) {
console.error(
"[react-nexacro] flushPendingChildren error:",
e,
);
}
}
flushPendingChildren(child, parentForm);
});
}
}
function addChildToParent(parentInstance, child) {
if (isPanel(parentInstance)) {
if (!parentInstance.parent) {
// 아직 Panel이 부모 Form에 추가되지 않았으므로 자식 추가를 보류한다.
if (!parentInstance._pendingChildren)
parentInstance._pendingChildren = [];
parentInstance._pendingChildren.push(child);
} else {
// 이미 부모 Form에 추가된 상태라면 바로 추가
const parentForm = getForm(parentInstance.parent);
if (parentForm && typeof parentForm.addChild === "function") {
try {
const existing = parentForm.components[child.id];
let shouldAdd = true;
if (existing) {
if (existing === child) {
shouldAdd = false;
} else {
try {
console.warn(`[Reconciler] COLLISION! Destroying old ${child.id} before adding new one.`);
parentForm.removeChild(child.id);
existing.destroy?.();
pendingLayoutRestores.add(parentForm);
} catch (ex) {}
}
}
if (shouldAdd) {
parentForm.addChild(child.id, child);
child.show?.();
if (child.__reactLayoutProps) {
pendingLayoutRestoreComps.add(child);
}
}
parentInstance.addItem(child.id, child.id);
markScrollForAncestors(parentInstance);
flushPendingChildren(child, parentForm);
relinkDependents(child, parentForm);
} catch (e) {
console.error(
"[react-nexacro] Error adding child to Panel's parent:",
e,
);
}
}
}
} else {
const form = getForm(parentInstance);
if (form && typeof form.addChild === "function") {
try {
const existing = form.components[child.id];
let shouldAdd = true;
if (existing) {
if (existing === child) {
shouldAdd = false;
} else {
try {
console.warn(`[Reconciler] COLLISION! Destroying old ${child.id} before adding new one.`);
form.removeChild(child.id);
existing.destroy?.();
pendingLayoutRestores.add(form);
} catch (ex) {}
}
}
if (shouldAdd) {
form.addChild(child.id, child);
child.show?.();
// 새 컴포넌트도 200ms 후 setTimeout에서 레이아웃(top/left 등)을 재평가받도록 큐에 추가
if (child.__reactLayoutProps) {
pendingLayoutRestoreComps.add(child);
}
}
markScrollForAncestors(parentInstance);
flushPendingChildren(child, form);
relinkDependents(child, form);
} catch (e) {
console.error(
"[react-nexacro] Error adding child to normal container:",
e,
);
}
}
}
}
/**
* 부모 컨테이너에서 자식을 제거한다.
* - Panel: panel.removeItem 후 부모 form에서 removeChild - Div/Form:
* getForm(parent).removeChild.
*
* @param {*} parentInstance 부모 넥사크로 컴포넌트 또는 Form.
* @param {*} child 자식 넥사크로 컴포넌트
* @access private
*/
function removeChildFromParent(parentInstance, child) {
if (isPanel(parentInstance)) {
if (parentInstance._pendingChildren) {
const idx = parentInstance._pendingChildren.indexOf(child);
if (idx > -1) {
parentInstance._pendingChildren.splice(idx, 1);
child.destroy?.();
return;
}
}
try {
parentInstance.removeItem(child.id);
} catch (e) {}
const parentForm = getForm(parentInstance.parent);
if (parentForm && typeof parentForm.removeChild === "function") {
try {
parentForm.removeChild(child.id);
markScrollForAncestors(parentInstance);
} catch (e) {}
}
} else {
const form = getForm(parentInstance);
if (form && typeof form.removeChild === "function") {
try {
form.removeChild(child.id);
markScrollForAncestors(parentInstance);
} catch (e) {}
}
}
child.destroy?.();
}
// ─── react-reconciler 호스트 설정 ────────────────────────────────────────────
const hostConfig = {
// 지원 기능 플래그
supportsMutation: true,
supportsPersistence: false,
supportsHydration: false,
isPrimaryRenderer: true,
noTimeout: -1,
// 현재 시간
now: Date.now,
// 스케줄링
scheduleTimeout: function (...args) {
return globalObj.setTimeout(...args);
},
cancelTimeout: function (...args) {
return globalObj.clearTimeout(...args);
},
// 루트 컨테이너 준비
prepareForCommit(containerInfo) {
try {
const form = getForm(containerInfo);
if (form && typeof form.set_enableredraw === "function") {
form.set_enableredraw(false);
pendingRedrawForms.add(form);
}
} catch (e) {}
return null;
},
resetAfterCommit(containerInfo) {
// 1. Z-Order/Child list order 복원 및 레이아웃 복구 작업을 "동기적"으로 먼저 수행한다.
const parentsToReorder = Array.from(pendingOrderRestoreParents);
pendingOrderRestoreParents.clear();
const compsToRestore = Array.from(pendingLayoutRestoreComps);
pendingLayoutRestoreComps.clear();
if (compsToRestore.length > 0 || parentsToReorder.length > 0) {
// Z-Order/Child list order 복원
parentsToReorder.forEach(parent => {
try {
if (parent && parent._reactChildren && parent._reactChildren.length > 0) {
if (isPanel(parent)) {
parent._reactChildren.forEach(child => {
if (child && typeof child.bringToFront === "function") {
child.bringToFront();
}
});
} else {
const form = getForm(parent);
if (form && form.components) {
// 리드로우 일시 중지
const hasRedraw = typeof form.set_enableredraw === "function";
if (hasRedraw) {
form.set_enableredraw(false);
}
// 모든 자식을 form에서 일시 분리
parent._reactChildren.forEach(child => {
try {
if (child && form.components[child.id]) {
form.removeChild(child.id);
}
} catch (e) {}
});
// 올바른 순서대로 다시 추가하여 인덱스 정렬
parent._reactChildren.forEach(child => {
try {
if (child) {
form.addChild(child.id, child);
child.show?.();
}
} catch (e) {}
});
// 리드로우 재개
if (hasRedraw) {
form.set_enableredraw(true);
}
}
}
}
} catch (e) {
}
});
// 레이아웃 복구 및 체인 재연결
const formsToReset = new Set();
compsToRestore.forEach((comp) => {
try {
if (comp && comp.__reactLayoutProps && Object.keys(comp.__reactLayoutProps).length > 0) {
const lp = comp.__reactLayoutProps;
const form = getForm(comp.parent);
applyLayoutProps(comp, lp);
if (form) {
formsToReset.add(form);
// 이 컴포넌트를 바라보는 sibling들의 체인 강제 재연결 (모든 방향)
fixComponentLayoutChains(comp, form);
}
}
} catch (e) {
}
});
// restore 및 chain fix가 완료된 후, 해당 form들의 resetScroll을 호출하여 fittocontents 갱신
formsToReset.forEach(form => {
try {
if (typeof form.resetScroll === "function") {
form.resetScroll();
}
} catch(e) {}
});
} else {
}
// 손상된 레이아웃 복원 (일반 패스 백업용)
pendingLayoutRestores.forEach((form) => {
if (form.components) {
Object.values(form.components).forEach(comp => {
try {
if (comp && comp.__reactLayoutProps) {
const lp = comp.__reactLayoutProps;
if (lp.top !== undefined && typeof comp.set_top === "function") {
comp.set_top(null);
comp.set_top(lp.top);
} else if (lp.bottom !== undefined && typeof comp.set_top === "function") {
comp.set_top(null);
}
if (lp.bottom !== undefined && typeof comp.set_bottom === "function") {
comp.set_bottom(null);
comp.set_bottom(lp.bottom);
} else if (lp.top !== undefined && typeof comp.set_bottom === "function") {
comp.set_bottom(null);
}
if (lp.left !== undefined && typeof comp.set_left === "function") {
comp.set_left(null);
comp.set_left(lp.left);
} else if (lp.right !== undefined && typeof comp.set_left === "function") {
comp.set_left(null);
}
if (lp.right !== undefined && typeof comp.set_right === "function") {
comp.set_right(null);
comp.set_right(lp.right);
} else if (lp.left !== undefined && typeof comp.set_right === "function") {
comp.set_right(null);
}
}
} catch (e) {}
});
}
});
pendingLayoutRestores.clear();
// 일괄 스크롤 초기화
pendingScrollForms.forEach((form) => {
try {
if (typeof form.resetScroll === "function") {
form.resetScroll();
}
} catch (e) {}
});
pendingScrollForms.clear();
// 2. 최종적으로 리드로우 재개
try {
const form = getForm(containerInfo);
if (form && typeof form.set_enableredraw === "function") {
form.set_enableredraw(true);
pendingRedrawForms.delete(form);
}
} catch (e) {}
pendingRedrawForms.forEach((form) => {
try {
if (typeof form.set_enableredraw === "function") {
form.set_enableredraw(true);
}
} catch (e) {}
});
pendingRedrawForms.clear();
},
preparePortalMount() {},
// 이벤트 우선순위
getCurrentEventPriority() {
return DefaultEventPriority;
},
// React 19 / react-reconciler 0.33 필수 메서드
setCurrentUpdatePriority(priority) {},
getCurrentUpdatePriority() {
return DefaultEventPriority;
},
resolveUpdatePriority() {
return DefaultEventPriority;
},
trackSchedulerEvent() {},
resolveEventType() {
return null;
},
resolveEventTimeStamp() {
return -1;
},
shouldAttemptEagerTransition() {
return false;
},
maySuspendCommit() {
return false;
},
maySuspendCommitOnUpdate() {
return false;
},
maySuspendCommitInSyncRender() {
return false;
},
preloadInstance() {
return true;
},
startSuspendingCommit() {},
suspendInstance() {},
waitForCommitToBeReady() {
return null;
},
getSuspendedCommitReason() {
return null;
},
resetFormInstance() {},
bindToConsole(methodName, args, isConcurrent) {
return console[methodName].bind(console, ...args);
},
supportsMicrotasks: true,
scheduleMicrotask: (fn) =>
Promise.resolve()
.then(fn)
.catch((e) =>
globalObj.setTimeout(() => {
throw e;
}),
),
getInstanceFromNode() {
return null;
},
beforeActiveInstanceBlur() {},
afterActiveInstanceBlur() {},
prepareScopeUpdate() {},
getInstanceFromScope() {
return null;
},
detachDeletedInstance() {},
// 루트 컨테이너 생성
createContainer(containerInfo) {
return containerInfo;
},
// 컴포넌트 인스턴스 생성
createInstance(type, props, rootContainer) {
const comp = createNexacroInstance(type, props, rootContainer);
applyProps(comp, props);
return comp;
},
// 텍스트 인스턴스 (넥사크로는 텍스트 노드 미지원 → 무시)
createTextInstance(text) {
return { __isTextNode: true, text };
},
// 자식 추가 (마운트 전 준비 단계)
appendInitialChild(parentInstance, child) {
if (child && child.__isTextNode) return;
recordChildInitial(parentInstance, child);
addChildToParent(parentInstance, child);
},
// 인스턴스 최종화 (자식 추가 완료 후)
finalizeInitialChildren() {
return false;
},
// 업데이트 준비 (변경 사항 계산)
prepareUpdate(instance, type, oldProps, newProps) {
return newProps;
},
// 자식 여부
shouldSetTextContent() {
return false;
},
// 루트 컨테이너 반환
getRootHostContext() {
return {};
},
// 자식 컨텍스트
getChildHostContext(parentHostContext) {
return parentHostContext;
},
// 공개 인스턴스 반환 (ref에 노출될 값)
getPublicInstance(instance) {
return instance;
},
// ── Mutation 메서드 ──────────────────────────────────────────────────────
// 컨테이너에 자식 추가 (최초 마운트)
appendChildToContainer(container, child) {
if (child && child.__isTextNode) return;
recordChildAppend(container, child);
// 루트 컨테이너(Div/Form)는 Panel이 아니므로 일반 경로
const form = getForm(container);
if (form && typeof form.addChild === "function") {
form.addChild(child.id, child);
child.show?.();
if (child.__reactLayoutProps) {
pendingLayoutRestoreComps.add(child);
}
markScrollForAncestors(container);
flushPendingChildren(child, form);
// 새 컴포넌트가 추가된 후, 이 컴포넌트를 top/left로 참조하는 기존 컴포넌트들을 재연결
relinkDependents(child, form);
}
},
// 부모에 자식 추가
appendChild(parentInstance, child) {
if (child && child.__isTextNode) return;
recordChildAppend(parentInstance, child);
addChildToParent(parentInstance, child);
},
// 특정 위치 앞에 자식 삽입
insertBefore(parentInstance, child, beforeChild) {
if (child && child.__isTextNode) return;
recordChildInsertBefore(parentInstance, child, beforeChild);
addChildToParent(parentInstance, child);
},
// 컨테이너에 특정 위치 앞에 자식 삽입
insertInContainerBefore(container, child, beforeChild) {
if (child && child.__isTextNode) return;
recordChildInsertBefore(container, child, beforeChild);
const form = getForm(container);
if (form && typeof form.addChild === "function") {
form.addChild(child.id, child);
child.show?.();
if (child.__reactLayoutProps) {
pendingLayoutRestoreComps.add(child);
}
markScrollForAncestors(container);
flushPendingChildren(child, form);
}
},
// 자식 제거
removeChild(parentInstance, child) {
if (child && child.__isTextNode) return;
recordChildRemove(parentInstance, child);
removeChildFromParent(parentInstance, child);
},
// 컨테이너에서 자식 제거
removeChildFromContainer(container, child) {
if (child && child.__isTextNode) return;
recordChildRemove(container, child);
const form = getForm(container);
if (form && typeof form.removeChild === "function") {
try {
form.removeChild(child.id);
} catch (e) {}
}
child.destroy?.();
},
// props 업데이트 커밋
// react-reconciler 0.33: (instance, type, oldProps, newProps) — updatePayload 파라미터 제거됨
commitUpdate(instance, type, oldProps, newProps) {
updateProps(instance, oldProps, newProps);
markScrollForAncestors(instance);
},
// 텍스트 업데이트 (무시)
commitTextUpdate() {},
// 마운트 후 효과 (commitMount가 필요한 경우)
commitMount() {},
// 숨기기/보이기 (Suspense 지원)
hideInstance(instance) {
try {
instance.visible = false;
} catch (e) {}
},
hideTextInstance() {},
unhideInstance(instance) {
try {
instance.visible = true;
} catch (e) {}
},
unhideTextInstance() {},
// 컨테이너 초기화
clearContainer(container) {
const form = getForm(container);
if (form && form.all) {
Array.from(form.all).forEach((child) => {
try {
form.removeChild(child.id);
} catch (e) {}
try {
child.destroy();
} catch (e) {}
});
}
},
};
export const NexacroReconciler = ReactReconciler(hostConfig);