ilha 0.6.0 → 0.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +6 -8
- package/dist/index.js +269 -34
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -51,6 +51,7 @@ interface IslandCall {
|
|
|
51
51
|
interface MarkedSignalAccessor<T> {
|
|
52
52
|
(): T;
|
|
53
53
|
(...args: [value: T]): void;
|
|
54
|
+
select<S>(selector: (state: T) => S): MarkedSignalAccessor<S>;
|
|
54
55
|
[SIGNAL_ACCESSOR]: true;
|
|
55
56
|
}
|
|
56
57
|
declare function ilhaRaw(value: string): RawHtml;
|
|
@@ -58,11 +59,11 @@ declare function ilhaCss(strings: TemplateStringsArray | string, ...values: (str
|
|
|
58
59
|
type JsxChild = unknown;
|
|
59
60
|
type JsxProps = Record<string, unknown> | null | undefined;
|
|
60
61
|
type JsxType = string | ((props: Record<string, unknown>) => unknown);
|
|
61
|
-
declare function ilhaJsx(type: JsxType, props: JsxProps, ...
|
|
62
|
+
declare function ilhaJsx(type: JsxType, props: JsxProps, maybeKey?: JsxChild | string | number, ...restChildren: JsxChild[]): RawHtml;
|
|
62
63
|
declare function ilhaFragment(props: {
|
|
63
64
|
children?: JsxChild;
|
|
64
65
|
} | null, ...children: JsxChild[]): RawHtml;
|
|
65
|
-
declare function ilhaJsxDEV(type: JsxType, props: JsxProps): RawHtml;
|
|
66
|
+
declare function ilhaJsxDEV(type: JsxType, props: JsxProps, maybeKey?: string | number, _source?: unknown, _self?: unknown): RawHtml;
|
|
66
67
|
declare function ilhaHtml(strings: TemplateStringsArray, ...values: unknown[]): RawHtml;
|
|
67
68
|
type ContextSignal<T> = {
|
|
68
69
|
(): T;
|
|
@@ -80,7 +81,7 @@ declare function ilhaContext<T>(key: string, initial: T): ContextSignal<T>;
|
|
|
80
81
|
* or `.render()` automatically subscribes the surrounding reactive scope —
|
|
81
82
|
* so when the signal changes, dependents re-run as if it were local state.
|
|
82
83
|
*/
|
|
83
|
-
declare function ilhaSignal<T>(initial: T):
|
|
84
|
+
declare function ilhaSignal<T>(initial: T): SignalAccessor<T>;
|
|
84
85
|
/**
|
|
85
86
|
* Run `fn` with reactive tracking suspended. Reading signals inside `fn`
|
|
86
87
|
* returns their current value without subscribing the surrounding scope.
|
|
@@ -115,10 +116,7 @@ interface DerivedEntry<TInput, TStateMap extends Record<string, unknown>> {
|
|
|
115
116
|
fn: DerivedFn<TInput, TStateMap, unknown>;
|
|
116
117
|
}
|
|
117
118
|
type IslandDerived<TDerivedMap extends Record<string, unknown>> = { readonly [K in keyof TDerivedMap]: DerivedValue<TDerivedMap[K]> };
|
|
118
|
-
|
|
119
|
-
(): T;
|
|
120
|
-
(value: T): void;
|
|
121
|
-
}
|
|
119
|
+
type ExternalSignal<T = unknown> = SignalAccessor<T>;
|
|
122
120
|
type SignalAccessor<T> = MarkedSignalAccessor<T>;
|
|
123
121
|
type MergeState<TStateMap extends Record<string, unknown>, K extends string, V> = Omit<TStateMap, K> & Record<K, V>;
|
|
124
122
|
type IslandState<TStateMap extends Record<string, unknown>> = { readonly [K in keyof TStateMap]-?: SignalAccessor<TStateMap[K]> };
|
|
@@ -243,7 +241,7 @@ interface MountOptions {
|
|
|
243
241
|
lazy?: boolean;
|
|
244
242
|
}
|
|
245
243
|
interface MountResult {
|
|
246
|
-
unmount: () => void
|
|
244
|
+
unmount: () => void | Promise<void>;
|
|
247
245
|
}
|
|
248
246
|
interface BuilderConfig<TInput, TStateMap extends Record<string, unknown>, TDerivedMap extends Record<string, unknown>> {
|
|
249
247
|
schema: StandardSchemaV1 | null;
|
package/dist/index.js
CHANGED
|
@@ -16,9 +16,76 @@ function shallowEqualInput(a, b) {
|
|
|
16
16
|
}
|
|
17
17
|
return true;
|
|
18
18
|
}
|
|
19
|
+
function extractDirectChildSlotIdsInOrder(html) {
|
|
20
|
+
if (typeof document === "undefined") return [];
|
|
21
|
+
const tpl = document.createElement("template");
|
|
22
|
+
tpl.innerHTML = html;
|
|
23
|
+
const root = tpl.content;
|
|
24
|
+
const ids = [];
|
|
25
|
+
for (const el of root.querySelectorAll("[data-ilha-slot]")) {
|
|
26
|
+
if (!(el instanceof Element)) continue;
|
|
27
|
+
let parent = el.parentElement ?? el.parentNode;
|
|
28
|
+
let nested = false;
|
|
29
|
+
while (parent && parent !== root) {
|
|
30
|
+
if (parent instanceof Element && parent.hasAttribute("data-ilha-slot")) {
|
|
31
|
+
nested = true;
|
|
32
|
+
break;
|
|
33
|
+
}
|
|
34
|
+
parent = parent instanceof Element ? parent.parentElement ?? parent.parentNode : parent.parentNode;
|
|
35
|
+
}
|
|
36
|
+
if (!nested) ids.push(el.getAttribute("data-ilha-slot"));
|
|
37
|
+
}
|
|
38
|
+
return ids;
|
|
39
|
+
}
|
|
40
|
+
function extractSlotPropsAttr(html, slotId) {
|
|
41
|
+
const escaped = slotId.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
42
|
+
const withProps = new RegExp(`<div\\s[^>]*data-ilha-slot="${escaped}"[^>]*data-ilha-props='([^']*)'`);
|
|
43
|
+
const m = html.match(withProps);
|
|
44
|
+
if (m) return m[1];
|
|
45
|
+
const withoutProps = new RegExp(`<div\\s[^>]*data-ilha-slot="${escaped}"`);
|
|
46
|
+
return html.match(withoutProps) ? "" : null;
|
|
47
|
+
}
|
|
48
|
+
function isStableInlineSlotMount(initialHtml, renderedHtml, slotIds) {
|
|
49
|
+
const initialOrder = extractDirectChildSlotIdsInOrder(initialHtml);
|
|
50
|
+
const renderedOrder = extractDirectChildSlotIdsInOrder(renderedHtml);
|
|
51
|
+
if (initialOrder.length !== renderedOrder.length || initialOrder.some((id, i) => id !== renderedOrder[i])) return false;
|
|
52
|
+
for (const id of slotIds) {
|
|
53
|
+
const initialProps = extractSlotPropsAttr(initialHtml, id);
|
|
54
|
+
const renderedProps = extractSlotPropsAttr(renderedHtml, id);
|
|
55
|
+
if (initialProps === null || renderedProps === null) return false;
|
|
56
|
+
if (initialProps !== renderedProps) return false;
|
|
57
|
+
}
|
|
58
|
+
return true;
|
|
59
|
+
}
|
|
60
|
+
function serializeSlotProps(props) {
|
|
61
|
+
return props === void 0 ? "" : JSON.stringify(props);
|
|
62
|
+
}
|
|
63
|
+
const MORPH_CONTROLLER_SLOT_SELECTOR = "[data-slot]";
|
|
64
|
+
const MORPH_CONTROLLER_ATTRS = new Set([
|
|
65
|
+
"data-checked",
|
|
66
|
+
"data-unchecked",
|
|
67
|
+
"data-indeterminate",
|
|
68
|
+
"aria-checked",
|
|
69
|
+
"data-open",
|
|
70
|
+
"data-closed",
|
|
71
|
+
"data-state",
|
|
72
|
+
"aria-expanded",
|
|
73
|
+
"aria-hidden",
|
|
74
|
+
"data-selected",
|
|
75
|
+
"data-panel-open"
|
|
76
|
+
]);
|
|
77
|
+
function shouldPreserveMorphAttr(el, name) {
|
|
78
|
+
return el.matches(MORPH_CONTROLLER_SLOT_SELECTOR) && MORPH_CONTROLLER_ATTRS.has(name);
|
|
79
|
+
}
|
|
19
80
|
function syncAttributes(from, to) {
|
|
20
|
-
for (const { name, value } of to.attributes)
|
|
21
|
-
|
|
81
|
+
for (const { name, value } of to.attributes) {
|
|
82
|
+
if (shouldPreserveMorphAttr(from, name)) continue;
|
|
83
|
+
if (from.getAttribute(name) !== value) from.setAttribute(name, value);
|
|
84
|
+
}
|
|
85
|
+
for (const { name } of Array.from(from.attributes)) {
|
|
86
|
+
if (shouldPreserveMorphAttr(from, name)) continue;
|
|
87
|
+
if (!to.hasAttribute(name)) from.removeAttribute(name);
|
|
88
|
+
}
|
|
22
89
|
}
|
|
23
90
|
function morphChildren(fromParent, toParent) {
|
|
24
91
|
const fromNodes = Array.from(fromParent.childNodes);
|
|
@@ -88,14 +155,14 @@ function dedentString(str) {
|
|
|
88
155
|
const indent = Math.min(...lines.filter((l) => l.trim() !== "").map((l) => l.match(/^(\s*)/)[1].length));
|
|
89
156
|
return lines.map((l) => l.slice(indent)).join("\n");
|
|
90
157
|
}
|
|
91
|
-
const RAW = Symbol("ilha.raw");
|
|
92
|
-
const SIGNAL_ACCESSOR = Symbol("ilha.signalAccessor");
|
|
93
|
-
const ISLAND = Symbol("ilha.island");
|
|
94
|
-
const ISLAND_CALL = Symbol("ilha.islandCall");
|
|
158
|
+
const RAW = Symbol.for("ilha.raw");
|
|
159
|
+
const SIGNAL_ACCESSOR = Symbol.for("ilha.signalAccessor");
|
|
160
|
+
const ISLAND = Symbol.for("ilha.island");
|
|
161
|
+
const ISLAND_CALL = Symbol.for("ilha.islandCall");
|
|
95
162
|
/** @internal Internal hook used by a parent's mountSlots to mount a child island and
|
|
96
163
|
* retain a handle to push updated props into it on subsequent parent
|
|
97
164
|
* re-renders. Not part of the public surface. */
|
|
98
|
-
const ISLAND_MOUNT_INTERNAL = Symbol("ilha.islandMountInternal");
|
|
165
|
+
const ISLAND_MOUNT_INTERNAL = Symbol.for("ilha.islandMountInternal");
|
|
99
166
|
const SLOT_ATTR = "data-ilha-slot";
|
|
100
167
|
const PROPS_ATTR = "data-ilha-props";
|
|
101
168
|
const STATE_ATTR = "data-ilha-state";
|
|
@@ -122,10 +189,19 @@ function currentRenderCtx() {
|
|
|
122
189
|
return renderCtxStack[renderCtxStack.length - 1];
|
|
123
190
|
}
|
|
124
191
|
function isIsland(v) {
|
|
125
|
-
|
|
192
|
+
if (typeof v !== "function") return false;
|
|
193
|
+
if (ISLAND in v) return true;
|
|
194
|
+
return Object.getOwnPropertySymbols(v).some((s) => s.description === "ilha.island");
|
|
126
195
|
}
|
|
127
196
|
function isIslandCall(v) {
|
|
128
|
-
|
|
197
|
+
if (v == null || typeof v !== "object" && typeof v !== "function") return false;
|
|
198
|
+
if (ISLAND_CALL in v) return true;
|
|
199
|
+
if (Object.getOwnPropertySymbols(v).some((s) => s.description === "ilha.islandCall")) return true;
|
|
200
|
+
if (typeof v === "object" && "island" in v) {
|
|
201
|
+
const island = v.island;
|
|
202
|
+
return typeof island === "function" && (ISLAND in island || Object.getOwnPropertySymbols(island).some((s) => s.description === "ilha.island"));
|
|
203
|
+
}
|
|
204
|
+
return false;
|
|
129
205
|
}
|
|
130
206
|
function emitIslandSlot(island, props, key) {
|
|
131
207
|
const ctx = currentRenderCtx();
|
|
@@ -171,10 +247,86 @@ async function resolveAsyncChildren(html, ctx) {
|
|
|
171
247
|
}
|
|
172
248
|
function markSignalAccessor(fn) {
|
|
173
249
|
fn[SIGNAL_ACCESSOR] = true;
|
|
174
|
-
|
|
250
|
+
const accessor = fn;
|
|
251
|
+
accessor.select = (selector) => createSelectAccessor(accessor, selector);
|
|
252
|
+
return accessor;
|
|
175
253
|
}
|
|
176
254
|
function isSignalAccessor(v) {
|
|
177
|
-
|
|
255
|
+
if (typeof v !== "function") return false;
|
|
256
|
+
if (SIGNAL_ACCESSOR in v) return true;
|
|
257
|
+
return Object.getOwnPropertySymbols(v).some((s) => s.description === "ilha.signalAccessor");
|
|
258
|
+
}
|
|
259
|
+
function getAtPath(obj, path) {
|
|
260
|
+
let cur = obj;
|
|
261
|
+
for (const seg of path) {
|
|
262
|
+
if (cur == null) return void 0;
|
|
263
|
+
cur = cur[seg];
|
|
264
|
+
}
|
|
265
|
+
return cur;
|
|
266
|
+
}
|
|
267
|
+
function setAtPath(obj, path, value) {
|
|
268
|
+
if (path.length === 0) return value;
|
|
269
|
+
const [head, ...rest] = path;
|
|
270
|
+
if (Array.isArray(obj)) {
|
|
271
|
+
const idx = head;
|
|
272
|
+
if (idx < 0 || idx >= obj.length) return obj;
|
|
273
|
+
const next = obj[idx];
|
|
274
|
+
const updated = rest.length === 0 ? value : setAtPath(next, rest, value);
|
|
275
|
+
if (Object.is(next, updated)) return obj;
|
|
276
|
+
const copy = obj.slice();
|
|
277
|
+
copy[idx] = updated;
|
|
278
|
+
return copy;
|
|
279
|
+
}
|
|
280
|
+
if (obj !== null && typeof obj === "object") {
|
|
281
|
+
const record = obj;
|
|
282
|
+
const key = String(head);
|
|
283
|
+
const next = record[key];
|
|
284
|
+
const updated = rest.length === 0 ? value : setAtPath(next, rest, value);
|
|
285
|
+
if (rest.length === 0) {
|
|
286
|
+
if (Object.is(next, value)) return obj;
|
|
287
|
+
} else if (Object.is(next, updated)) return obj;
|
|
288
|
+
return {
|
|
289
|
+
...record,
|
|
290
|
+
[key]: updated
|
|
291
|
+
};
|
|
292
|
+
}
|
|
293
|
+
return rest.length === 0 ? value : setAtPath(void 0, rest, value);
|
|
294
|
+
}
|
|
295
|
+
function toPathSegment(prop) {
|
|
296
|
+
if (typeof prop === "symbol") return null;
|
|
297
|
+
if (prop === "length") return null;
|
|
298
|
+
if (/^\d+$/.test(prop)) return Number(prop);
|
|
299
|
+
return prop;
|
|
300
|
+
}
|
|
301
|
+
function trackSelectPath(rootState, selector) {
|
|
302
|
+
const path = [];
|
|
303
|
+
const track = (value) => {
|
|
304
|
+
if (value === null || typeof value !== "object" && typeof value !== "function") return value;
|
|
305
|
+
return new Proxy(value, { get(target, prop, receiver) {
|
|
306
|
+
const seg = toPathSegment(prop);
|
|
307
|
+
if (seg != null) path.push(seg);
|
|
308
|
+
const next = Reflect.get(target, prop, receiver);
|
|
309
|
+
return seg != null && next !== null && typeof next === "object" ? track(next) : next;
|
|
310
|
+
} });
|
|
311
|
+
};
|
|
312
|
+
selector(track(rootState));
|
|
313
|
+
return path;
|
|
314
|
+
}
|
|
315
|
+
function createSelectAccessor(root, selector) {
|
|
316
|
+
const path = trackSelectPath(root(), selector);
|
|
317
|
+
const selected = selector(root());
|
|
318
|
+
const resolved = path.length === 0 ? root() : getAtPath(root(), path);
|
|
319
|
+
if (!Object.is(selected, resolved)) {
|
|
320
|
+
const msg = "select(): selector must only traverse nested properties or array indexes — derived or transformed values are not supported.";
|
|
321
|
+
if (__DEV__) warn(msg);
|
|
322
|
+
throw new Error(msg);
|
|
323
|
+
}
|
|
324
|
+
if (__DEV__ && path.length === 0) warn("select(): selector did not traverse nested state — bind writes may replace the entire root value.");
|
|
325
|
+
return markSignalAccessor((...args) => {
|
|
326
|
+
if (args.length === 0) return path.length === 0 ? selector(root()) : getAtPath(root(), path);
|
|
327
|
+
const next = path.length === 0 ? args[0] : setAtPath(root(), path, args[0]);
|
|
328
|
+
if (!Object.is(root(), next)) root(next);
|
|
329
|
+
});
|
|
178
330
|
}
|
|
179
331
|
function ilhaRaw(value) {
|
|
180
332
|
return {
|
|
@@ -229,6 +381,19 @@ function normalizeJsxChildren(props, children) {
|
|
|
229
381
|
const propChildren = props && "children" in props ? props.children : void 0;
|
|
230
382
|
return (children.length > 0 ? children : propChildren === void 0 ? [] : [propChildren]).flat(1);
|
|
231
383
|
}
|
|
384
|
+
function normalizeJsxSlotKey(raw) {
|
|
385
|
+
const key = String(raw);
|
|
386
|
+
if (key.trim().length === 0) throw new Error("jsx key requires a non-empty string.");
|
|
387
|
+
if (key.includes(":")) throw new Error(`jsx key cannot contain the slot separator ":" (got "${key}").`);
|
|
388
|
+
return key;
|
|
389
|
+
}
|
|
390
|
+
/** React-style `key` (props or 3rd jsx arg) on island components → `k:{key}` slot ids. */
|
|
391
|
+
function extractJsxSlotKey(props, keyArg) {
|
|
392
|
+
const fromProps = props?.key;
|
|
393
|
+
const raw = keyArg ?? (typeof fromProps === "string" || typeof fromProps === "number" ? fromProps : void 0);
|
|
394
|
+
if (raw == null) return void 0;
|
|
395
|
+
return normalizeJsxSlotKey(raw);
|
|
396
|
+
}
|
|
232
397
|
function serializeStyle(value) {
|
|
233
398
|
return Object.entries(value).map(([k, v]) => {
|
|
234
399
|
if (!SAFE_CSS_PROP_RE.test(k)) return "";
|
|
@@ -281,17 +446,25 @@ function renderJsxElement(type, props, children) {
|
|
|
281
446
|
} else if (children.length > 0 && __DEV__) warn(`Void element <${type}> should not have children. They will be ignored.`);
|
|
282
447
|
return ilhaHtml(chunks, ...values);
|
|
283
448
|
}
|
|
284
|
-
function ilhaJsx(type, props, ...
|
|
285
|
-
const
|
|
449
|
+
function ilhaJsx(type, props, maybeKey, ...restChildren) {
|
|
450
|
+
const hasKeyArg = typeof maybeKey === "string" || typeof maybeKey === "number";
|
|
451
|
+
const keyFromArg = hasKeyArg ? maybeKey : void 0;
|
|
452
|
+
const normalizedChildren = normalizeJsxChildren(props, hasKeyArg || maybeKey === void 0 ? restChildren : [maybeKey, ...restChildren]);
|
|
286
453
|
if (typeof type === "function") {
|
|
454
|
+
const slotKey = extractJsxSlotKey(props, typeof keyFromArg === "string" || typeof keyFromArg === "number" ? keyFromArg : void 0);
|
|
287
455
|
const componentProps = {
|
|
288
456
|
...props ?? {},
|
|
289
457
|
...normalizedChildren.length > 0 ? { children: normalizedChildren } : {}
|
|
290
458
|
};
|
|
291
459
|
delete componentProps["key"];
|
|
292
460
|
const out = type(Object.keys(componentProps).length ? componentProps : {});
|
|
461
|
+
if (isIslandCall(out)) {
|
|
462
|
+
if (slotKey !== void 0) out.key = slotKey;
|
|
463
|
+
return ilhaHtml`${out}`;
|
|
464
|
+
}
|
|
465
|
+
if (isRawHtml(out)) return out;
|
|
466
|
+
if (typeof out === "string" && isIsland(type)) return ilhaRaw(emitIslandSlot(type, componentProps, slotKey));
|
|
293
467
|
if (typeof out === "string") return ilhaHtml`${out}`;
|
|
294
|
-
if (out && typeof out === "object" && RAW in out) return out;
|
|
295
468
|
return ilhaHtml`${out}`;
|
|
296
469
|
}
|
|
297
470
|
return renderJsxElement(type, props, normalizedChildren);
|
|
@@ -300,13 +473,13 @@ function ilhaFragment(props, ...children) {
|
|
|
300
473
|
const normalizedChildren = normalizeJsxChildren(props, children);
|
|
301
474
|
return ilhaHtml(["", ...normalizedChildren.map(() => "")], ...normalizedChildren);
|
|
302
475
|
}
|
|
303
|
-
function ilhaJsxDEV(type, props) {
|
|
304
|
-
return ilhaJsx(type, props);
|
|
476
|
+
function ilhaJsxDEV(type, props, maybeKey, _source, _self) {
|
|
477
|
+
return ilhaJsx(type, props, maybeKey);
|
|
305
478
|
}
|
|
306
479
|
function interpolateValue(v) {
|
|
307
480
|
if (v == null || v === true || v === false) return "";
|
|
308
481
|
if (Array.isArray(v)) return v.map(interpolateValue).join("");
|
|
309
|
-
if (
|
|
482
|
+
if (isRawHtml(v)) return v.value;
|
|
310
483
|
if (isIslandCall(v)) {
|
|
311
484
|
const call = typeof v === "function" ? v() : v;
|
|
312
485
|
return emitIslandSlot(call.island, call.props, call.key);
|
|
@@ -432,8 +605,15 @@ function ilhaHtml(strings, ...values) {
|
|
|
432
605
|
value: dedentString(result)
|
|
433
606
|
};
|
|
434
607
|
}
|
|
608
|
+
/** Duck-type RawHtml — works across duplicate `ilha` bundles (distinct RAW symbols). */
|
|
609
|
+
function isRawHtml(v) {
|
|
610
|
+
if (typeof v !== "object" || v === null || !("value" in v)) return false;
|
|
611
|
+
if (typeof v.value !== "string") return false;
|
|
612
|
+
if (RAW in v) return true;
|
|
613
|
+
return Object.getOwnPropertySymbols(v).some((s) => s.description === "ilha.raw");
|
|
614
|
+
}
|
|
435
615
|
function unwrapHtml(v) {
|
|
436
|
-
return
|
|
616
|
+
return isRawHtml(v) ? v.value : v;
|
|
437
617
|
}
|
|
438
618
|
const contextRegistry = /* @__PURE__ */ new Map();
|
|
439
619
|
function ilhaContext(key, initial) {
|
|
@@ -1161,10 +1341,38 @@ Element: ${host.outerHTML.slice(0, 120)}`);
|
|
|
1161
1341
|
}
|
|
1162
1342
|
}
|
|
1163
1343
|
const mountedSlots = /* @__PURE__ */ new Map();
|
|
1344
|
+
const leavingSlots = /* @__PURE__ */ new Map();
|
|
1345
|
+
function teardownMountedSlot(id, entry) {
|
|
1346
|
+
mountedSlots.delete(id);
|
|
1347
|
+
const token = Symbol();
|
|
1348
|
+
const result = entry.unmount();
|
|
1349
|
+
const remove = (stub) => {
|
|
1350
|
+
const leaving = leavingSlots.get(id);
|
|
1351
|
+
if (!leaving || leaving.token !== token) return;
|
|
1352
|
+
leaving.el.remove();
|
|
1353
|
+
stub?.remove();
|
|
1354
|
+
leavingSlots.delete(id);
|
|
1355
|
+
};
|
|
1356
|
+
if (result instanceof Promise) {
|
|
1357
|
+
let stub;
|
|
1358
|
+
if (entry.el.isConnected) {
|
|
1359
|
+
stub = document.createElement("div");
|
|
1360
|
+
stub.setAttribute(SLOT_ATTR, id);
|
|
1361
|
+
entry.el.replaceWith(stub);
|
|
1362
|
+
}
|
|
1363
|
+
leavingSlots.set(id, {
|
|
1364
|
+
el: entry.el,
|
|
1365
|
+
token
|
|
1366
|
+
});
|
|
1367
|
+
result.finally(() => remove(stub));
|
|
1368
|
+
} else entry.el.remove();
|
|
1369
|
+
}
|
|
1164
1370
|
function findSlot(id) {
|
|
1371
|
+
const leavingEls = new Set([...leavingSlots.values()].map((l) => l.el));
|
|
1165
1372
|
const escaped = typeof CSS !== "undefined" && CSS.escape ? CSS.escape(id) : id.replace(/["\\]/g, "\\$&");
|
|
1166
1373
|
const candidates = host.querySelectorAll(`[${SLOT_ATTR}="${escaped}"]`);
|
|
1167
1374
|
for (const candidate of candidates) {
|
|
1375
|
+
if (leavingEls.has(candidate)) continue;
|
|
1168
1376
|
let el = candidate;
|
|
1169
1377
|
while (el && el !== host) {
|
|
1170
1378
|
if (el.hasAttribute("data-ilha")) break;
|
|
@@ -1175,10 +1383,7 @@ Element: ${host.outerHTML.slice(0, 120)}`);
|
|
|
1175
1383
|
return null;
|
|
1176
1384
|
}
|
|
1177
1385
|
function mountSlots(slotMap) {
|
|
1178
|
-
for (const [id, entry] of mountedSlots) if (!slotMap.has(id))
|
|
1179
|
-
entry.unmount();
|
|
1180
|
-
mountedSlots.delete(id);
|
|
1181
|
-
}
|
|
1386
|
+
for (const [id, entry] of mountedSlots) if (!slotMap.has(id)) teardownMountedSlot(id, entry);
|
|
1182
1387
|
for (const [id, { island: childIsland, props }] of slotMap) {
|
|
1183
1388
|
const existing = mountedSlots.get(id);
|
|
1184
1389
|
if (existing) {
|
|
@@ -1229,6 +1434,7 @@ Element: ${host.outerHTML.slice(0, 120)}`);
|
|
|
1229
1434
|
listeners.splice(0, listeners.length, ...listeners.filter((l) => l.entry !== entry));
|
|
1230
1435
|
}
|
|
1231
1436
|
const eventTarget = event.target instanceof Element ? event.target : listenerTarget;
|
|
1437
|
+
const handlerTarget = entry.selector === "" ? eventTarget : listenerTarget;
|
|
1232
1438
|
let handlerSignal;
|
|
1233
1439
|
if (entry.abortable) {
|
|
1234
1440
|
let entryMap = invocationControllers.get(entry);
|
|
@@ -1250,7 +1456,7 @@ Element: ${host.outerHTML.slice(0, 120)}`);
|
|
|
1250
1456
|
derived,
|
|
1251
1457
|
input,
|
|
1252
1458
|
host,
|
|
1253
|
-
target:
|
|
1459
|
+
target: handlerTarget,
|
|
1254
1460
|
event,
|
|
1255
1461
|
signal: handlerSignal
|
|
1256
1462
|
});
|
|
@@ -1291,11 +1497,11 @@ Element: ${host.outerHTML.slice(0, 120)}`);
|
|
|
1291
1497
|
input
|
|
1292
1498
|
}), hasExistingContent ? host : void 0);
|
|
1293
1499
|
if (!hasExistingContent) host.innerHTML = stylePrefix + initial.html;
|
|
1294
|
-
attachListeners();
|
|
1295
1500
|
let stopBindings = applyTemplateBindings(host, initial.binds);
|
|
1296
1501
|
cleanups.push(() => stopBindings());
|
|
1297
1502
|
mountSlots(initial.slots);
|
|
1298
1503
|
cleanups.push(() => mountedSlots.forEach((entry) => entry.unmount()));
|
|
1504
|
+
attachListeners();
|
|
1299
1505
|
if (!shouldSkipOnMount) for (const entry of onMounts) {
|
|
1300
1506
|
const prevSub = setActiveSub(void 0);
|
|
1301
1507
|
let userCleanup;
|
|
@@ -1365,19 +1571,34 @@ Element: ${host.outerHTML.slice(0, 120)}`);
|
|
|
1365
1571
|
if (!initialized) {
|
|
1366
1572
|
initialized = true;
|
|
1367
1573
|
if (rendered === initialRenderedHtml) return;
|
|
1574
|
+
if (mountedSlots.size > 0 && mountedSlots.size === newSlotMap.size && [...newSlotMap.keys()].every((id) => mountedSlots.has(id)) && isStableInlineSlotMount(initialRenderedHtml, rendered, newSlotMap.keys())) return;
|
|
1368
1575
|
}
|
|
1369
1576
|
detachListeners();
|
|
1370
1577
|
stopBindings();
|
|
1578
|
+
const prevMountedCount = mountedSlots.size;
|
|
1579
|
+
const removedSlotIds = [];
|
|
1371
1580
|
for (const [id, entry] of mountedSlots) if (!newSlotMap.has(id)) {
|
|
1372
|
-
entry
|
|
1373
|
-
|
|
1581
|
+
teardownMountedSlot(id, entry);
|
|
1582
|
+
removedSlotIds.push(id);
|
|
1583
|
+
}
|
|
1584
|
+
for (const id of removedSlotIds) {
|
|
1585
|
+
if (leavingSlots.has(id)) continue;
|
|
1586
|
+
const escaped = typeof CSS !== "undefined" && CSS.escape ? CSS.escape(id) : id.replace(/["\\]/g, "\\$&");
|
|
1587
|
+
for (const el of host.querySelectorAll(`[${SLOT_ATTR}="${escaped}"]`)) el.remove();
|
|
1374
1588
|
}
|
|
1375
1589
|
const preserved = /* @__PURE__ */ new Map();
|
|
1590
|
+
const listShrunk = newSlotMap.size < prevMountedCount;
|
|
1376
1591
|
for (const [id, entry] of mountedSlots) {
|
|
1377
1592
|
if (!newSlotMap.has(id)) continue;
|
|
1378
1593
|
if (!entry.el.isConnected) continue;
|
|
1594
|
+
if (listShrunk && id.startsWith("p:")) {
|
|
1595
|
+
teardownMountedSlot(id, entry);
|
|
1596
|
+
continue;
|
|
1597
|
+
}
|
|
1379
1598
|
const stub = document.createElement("div");
|
|
1380
1599
|
stub.setAttribute(SLOT_ATTR, id);
|
|
1600
|
+
const incomingProps = serializeSlotProps(newSlotMap.get(id)?.props);
|
|
1601
|
+
if (incomingProps !== "") stub.setAttribute(PROPS_ATTR, incomingProps);
|
|
1381
1602
|
entry.el.replaceWith(stub);
|
|
1382
1603
|
preserved.set(id, entry.el);
|
|
1383
1604
|
}
|
|
@@ -1400,16 +1621,23 @@ Element: ${host.outerHTML.slice(0, 120)}`);
|
|
|
1400
1621
|
if (__DEV__ && _mountedHosts) _mountedHosts.delete(host);
|
|
1401
1622
|
stopRender();
|
|
1402
1623
|
detachListeners();
|
|
1624
|
+
const pending = [];
|
|
1625
|
+
for (const [, entry] of mountedSlots) {
|
|
1626
|
+
const result = entry.unmount();
|
|
1627
|
+
if (result instanceof Promise) pending.push(result);
|
|
1628
|
+
}
|
|
1403
1629
|
if (transition?.leave) {
|
|
1404
1630
|
const result = transition.leave(host);
|
|
1405
|
-
if (result instanceof Promise)
|
|
1406
|
-
result.then(() => {
|
|
1407
|
-
for (const c of cleanups) c();
|
|
1408
|
-
}).catch(console.error);
|
|
1409
|
-
return;
|
|
1410
|
-
}
|
|
1631
|
+
if (result instanceof Promise) pending.push(result);
|
|
1411
1632
|
}
|
|
1412
|
-
|
|
1633
|
+
const finish = () => {
|
|
1634
|
+
for (const c of cleanups) c();
|
|
1635
|
+
};
|
|
1636
|
+
if (pending.length > 0) return Promise.all(pending).then(finish).catch((err) => {
|
|
1637
|
+
console.error(err);
|
|
1638
|
+
finish();
|
|
1639
|
+
});
|
|
1640
|
+
finish();
|
|
1413
1641
|
};
|
|
1414
1642
|
const updateProps = (nextProps) => {
|
|
1415
1643
|
if (tornDown) return;
|
|
@@ -1569,7 +1797,14 @@ function mountAll(registry, options = {}) {
|
|
|
1569
1797
|
io.disconnect();
|
|
1570
1798
|
});
|
|
1571
1799
|
} else els.forEach(activateEl);
|
|
1572
|
-
return { unmount: () =>
|
|
1800
|
+
return { unmount: () => {
|
|
1801
|
+
const pending = [];
|
|
1802
|
+
for (const u of unmounts) {
|
|
1803
|
+
const result = u();
|
|
1804
|
+
if (result instanceof Promise) pending.push(result);
|
|
1805
|
+
}
|
|
1806
|
+
if (pending.length > 0) return Promise.all(pending).then(() => {});
|
|
1807
|
+
} };
|
|
1573
1808
|
}
|
|
1574
1809
|
const rootBuilder = new IlhaBuilder({
|
|
1575
1810
|
schema: null,
|