@atolljs/islands 0.1.1 → 0.1.2
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/README.md +4 -4
- package/package.json +3 -3
- package/src/island.ts +64 -9
package/README.md
CHANGED
|
@@ -5,7 +5,7 @@ thread. A real framework renderer commits against a DOM-free host surface in
|
|
|
5
5
|
the worker; every commit serializes to an op stream the main thread replays as
|
|
6
6
|
DOM mutations.
|
|
7
7
|
|
|
8
|
-
**[Atoll — package documentation](https://jwhenry3.github.io/atolljs/consumer
|
|
8
|
+
**[Atoll — package documentation](https://jwhenry3.github.io/atolljs/consumer/quickstart/)**
|
|
9
9
|
|
|
10
10
|
The vocabulary:
|
|
11
11
|
|
|
@@ -140,10 +140,10 @@ const island = await mountIsland({ worker: () => new Worker(url, { type: 'module
|
|
|
140
140
|
|
|
141
141
|
## Documentation
|
|
142
142
|
|
|
143
|
-
- [Atoll — package documentation](https://jwhenry3.github.io/atolljs/consumer
|
|
144
|
-
- [Worker islands](https://jwhenry3.github.io/atolljs/consumer
|
|
143
|
+
- [Atoll — package documentation](https://jwhenry3.github.io/atolljs/consumer/quickstart/)
|
|
144
|
+
- [Worker islands](https://jwhenry3.github.io/atolljs/consumer/islands/) —
|
|
145
145
|
mountIsland options, `IslandHandle`, transport modes
|
|
146
|
-
- [Writing island apps](https://jwhenry3.github.io/atolljs/consumer
|
|
146
|
+
- [Writing island apps](https://jwhenry3.github.io/atolljs/consumer/island-apps/) —
|
|
147
147
|
emit/callbackProp/slots, instance discipline
|
|
148
148
|
- In-repo internals: [`docs/islands.md`](../../docs/islands.md),
|
|
149
149
|
[`docs/islands-worker.md`](../../docs/islands-worker.md),
|
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@atolljs/islands",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"description": "Atoll islands — MonoWorker/PolyWorker hosts rendering framework islands (React/Vue/Svelte/Solid/Angular) over a proxy-DOM op protocol",
|
|
5
|
-
"homepage": "https://jwhenry3.github.io/atolljs/consumer
|
|
5
|
+
"homepage": "https://jwhenry3.github.io/atolljs/consumer/quickstart/",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"exports": {
|
|
8
8
|
".": "./src/index.ts",
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
"htmlparser2": "^12.0.0"
|
|
20
20
|
},
|
|
21
21
|
"peerDependencies": {
|
|
22
|
-
"@atolljs/core": "0.1.
|
|
22
|
+
"@atolljs/core": "0.1.2"
|
|
23
23
|
},
|
|
24
24
|
"repository": {
|
|
25
25
|
"type": "git",
|
package/src/island.ts
CHANGED
|
@@ -299,6 +299,40 @@ const assertCloneableProps = (props: Record<string, unknown>, context: string):
|
|
|
299
299
|
);
|
|
300
300
|
};
|
|
301
301
|
|
|
302
|
+
/* ── Style values ─────────────────────────────────────────────────────────
|
|
303
|
+
* Style objects cross the wire verbatim (`style={{ height: 24000000 }}`),
|
|
304
|
+
* so the driver must apply the same unit semantics react-dom does:
|
|
305
|
+
* a nonzero number on a non-unitless property needs 'px' — assigning the
|
|
306
|
+
* bare number to el.style silently no-ops on every real browser.
|
|
307
|
+
* The list is React DOM's isUnitlessNumber set (which is also the CSS spec's
|
|
308
|
+
* unitless property surface). */
|
|
309
|
+
const UNITLESS_CSS = new Set([
|
|
310
|
+
'animationIterationCount', 'aspectRatio', 'borderImageOutset',
|
|
311
|
+
'borderImageSlice', 'borderImageWidth', 'boxFlex', 'boxFlexGroup',
|
|
312
|
+
'boxOrdinalGroup', 'columnCount', 'columns', 'flex', 'flexGrow',
|
|
313
|
+
'flexPositive', 'flexShrink', 'flexNegative', 'flexOrder', 'gridArea',
|
|
314
|
+
'gridRow', 'gridRowEnd', 'gridRowSpan', 'gridRowStart', 'gridColumn',
|
|
315
|
+
'gridColumnEnd', 'gridColumnSpan', 'gridColumnStart', 'fontWeight',
|
|
316
|
+
'lineClamp', 'lineHeight', 'opacity', 'order', 'orphans', 'scale',
|
|
317
|
+
'tabSize', 'widows', 'zIndex', 'zoom', 'fillOpacity', 'floodOpacity',
|
|
318
|
+
'stopOpacity', 'strokeDasharray', 'strokeDashoffset', 'strokeMiterlimit',
|
|
319
|
+
'strokeOpacity', 'strokeWidth',
|
|
320
|
+
]);
|
|
321
|
+
|
|
322
|
+
/** Coerce a wire style value to a CSS string — px-suffixes bare numbers
|
|
323
|
+
* on non-unitless keys (custom properties pass verbatim). */
|
|
324
|
+
const cssStyleValue = (key: string, value: unknown): string => {
|
|
325
|
+
if (
|
|
326
|
+
typeof value === 'number' &&
|
|
327
|
+
value !== 0 &&
|
|
328
|
+
!key.startsWith('--') &&
|
|
329
|
+
!UNITLESS_CSS.has(key)
|
|
330
|
+
) {
|
|
331
|
+
return `${value}px`;
|
|
332
|
+
}
|
|
333
|
+
return String(value);
|
|
334
|
+
};
|
|
335
|
+
|
|
302
336
|
export async function mountIsland(opts: MountIslandOptions): Promise<IslandHandle> {
|
|
303
337
|
const {
|
|
304
338
|
el, onEvent, onActivity, onOps, slots, mode: initialMode, mountTimeout,
|
|
@@ -436,11 +470,17 @@ export async function mountIsland(opts: MountIslandOptions): Promise<IslandHandl
|
|
|
436
470
|
return;
|
|
437
471
|
}
|
|
438
472
|
if (name === 'style' && typeof value === 'object' && value !== null) {
|
|
439
|
-
const
|
|
473
|
+
const decl = (el as HTMLElement).style;
|
|
474
|
+
const elStyle = decl as unknown as Record<string, string>;
|
|
440
475
|
const prevStyle = (prevProps.get(id)?.style ?? {}) as Record<string, string>;
|
|
441
|
-
const nextStyle = value as Record<string,
|
|
476
|
+
const nextStyle = value as Record<string, unknown>;
|
|
442
477
|
for (const k of Object.keys(prevStyle)) if (!(k in nextStyle)) elStyle[k] = '';
|
|
443
|
-
for (const [k, v] of Object.entries(nextStyle))
|
|
478
|
+
for (const [k, v] of Object.entries(nextStyle)) {
|
|
479
|
+
if (prevStyle[k] !== v) {
|
|
480
|
+
if (k.startsWith('--') || k.includes('-')) decl.setProperty(k, cssStyleValue(k, v));
|
|
481
|
+
else elStyle[k] = cssStyleValue(k, v);
|
|
482
|
+
}
|
|
483
|
+
}
|
|
444
484
|
return;
|
|
445
485
|
}
|
|
446
486
|
if (name === 'className') {
|
|
@@ -555,7 +595,12 @@ export async function mountIsland(opts: MountIslandOptions): Promise<IslandHandl
|
|
|
555
595
|
}
|
|
556
596
|
if (name in node) {
|
|
557
597
|
try {
|
|
558
|
-
|
|
598
|
+
// Presence-attr semantics: the proxy emits attr{name,''} for boolean
|
|
599
|
+
// attributes (checked, disabled…) — assigning '' would read falsy.
|
|
600
|
+
// A boolean live property takes true whenever the attr is present.
|
|
601
|
+
const cur = (node as unknown as Record<string, unknown>)[name];
|
|
602
|
+
(node as unknown as Record<string, unknown>)[name] =
|
|
603
|
+
typeof cur === 'boolean' ? true : value;
|
|
559
604
|
return;
|
|
560
605
|
} catch {
|
|
561
606
|
/* read-only property — use the attribute */
|
|
@@ -576,17 +621,18 @@ export async function mountIsland(opts: MountIslandOptions): Promise<IslandHandl
|
|
|
576
621
|
const prev = prevProps.get(id) ?? {};
|
|
577
622
|
const merged = { ...((prev.style ?? {}) as Record<string, string>) };
|
|
578
623
|
for (const [k, v] of Object.entries(changes)) {
|
|
624
|
+
const cv = cssStyleValue(k, v);
|
|
579
625
|
if (k.startsWith('--') || k.includes('-')) {
|
|
580
626
|
// Custom properties and kebab-case keys live outside the camelCase
|
|
581
627
|
// property surface — el.style['font-size'] is a silent no-op on real
|
|
582
628
|
// DOM; setProperty accepts kebab-case, and '' clears either kind.
|
|
583
|
-
elStyle.setProperty(k,
|
|
584
|
-
} else if (
|
|
629
|
+
elStyle.setProperty(k, cv);
|
|
630
|
+
} else if (cv.endsWith('!important')) {
|
|
585
631
|
// `style.setProperty(k, v, 'important')` crosses as a suffix — the
|
|
586
632
|
// style op has no separate priority channel.
|
|
587
|
-
elStyle.setProperty(k,
|
|
633
|
+
elStyle.setProperty(k, cv.replace(/\s*!important$/, ''), 'important');
|
|
588
634
|
} else {
|
|
589
|
-
elStyleMap[k] =
|
|
635
|
+
elStyleMap[k] = cv;
|
|
590
636
|
}
|
|
591
637
|
merged[k] = v;
|
|
592
638
|
}
|
|
@@ -707,6 +753,10 @@ export async function mountIsland(opts: MountIslandOptions): Promise<IslandHandl
|
|
|
707
753
|
// node maps must survive this op: the following `append` still needs
|
|
708
754
|
// the ids it just created. Stale ids are never reused; the only cost
|
|
709
755
|
// is a few dead map entries on remount.
|
|
756
|
+
// Slot anchors inside get no remove op of their own — unmount them
|
|
757
|
+
// like a `remove` op would so slots callbacks see null and
|
|
758
|
+
// slotNodes doesn't leak.
|
|
759
|
+
unmountSlotSubtree(el);
|
|
710
760
|
el.replaceChildren();
|
|
711
761
|
break;
|
|
712
762
|
}
|
|
@@ -897,7 +947,12 @@ export async function mountIsland(opts: MountIslandOptions): Promise<IslandHandl
|
|
|
897
947
|
void client
|
|
898
948
|
.unmount(instance)
|
|
899
949
|
.catch(() => {})
|
|
900
|
-
.finally(() =>
|
|
950
|
+
.finally(() => {
|
|
951
|
+
// A remount may have registered while teardown was in flight
|
|
952
|
+
// (app/client input swap does destroy → mount back-to-back) —
|
|
953
|
+
// terminate only when the client is still truly idle.
|
|
954
|
+
if ((clientMounts.get(client) ?? 0) === 0) client.terminate();
|
|
955
|
+
});
|
|
901
956
|
}
|
|
902
957
|
},
|
|
903
958
|
};
|