@symbiote-native/svelte 3.0.3 → 3.0.5

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.
@@ -12,8 +12,7 @@
12
12
  //
13
13
  // This file authors the raw `scroll-view` intrinsic directly, and needs to: it walks an indexable
14
14
  // `plan.cells` list rather than taking an opaque children Snippet, so it can mark sticky cells
15
- // itself. (There is no ScrollView component to render instead — it was deleted 2026-09-10 and an
16
- // app writes the tag too.)
15
+ // itself. (There is no ScrollView component to render instead — an app writes the tag too.)
17
16
  //
18
17
  // It does NOT author the CONTENT node. `registerScrollViewBehavior()` puts a `buildStructure` on
19
18
  // the scroll tags, and exactly one thing may build `RCTScrollContentView` — emitting one here as
@@ -63,12 +63,9 @@
63
63
  Platform.OS,
64
64
  ),
65
65
  );
66
- // DIAGNOSTIC (2026-08-13, tracking why stickyHeaderIndices comes back undefined on Android
67
- // despite the demo passing the bare `stickySectionHeadersEnabled` shorthand): the previous
68
- // logging only showed the RESULT (VirtualizedList's own dlog), never the INPUTS this $derived
69
- // actually read — isolated verification proved bare-shorthand forwarding through this exact
70
- // two-hop generic-component chain works correctly in a standalone repro, so this must show
71
- // what differs in the real component tree.
66
+ // Diagnostic: logs every INPUT this $derived reads, not just VirtualizedList's own result —
67
+ // `stickyHeaderIndices` can come back undefined on Android with the bare
68
+ // `stickySectionHeadersEnabled` shorthand, and only the inputs show what differs.
72
69
  $effect(() => {
73
70
  dlog(
74
71
  `VirtualizedSectionList sticky-inputs enabled=${String(props.stickySectionHeadersEnabled)} ` +
@@ -1,9 +1,6 @@
1
- // Portal — the Svelte adapter's same-surface portal: the twin of React's `createPortal`
2
- // (adapters/react/src/create-portal/index.ts), Solid's `<Portal mount={…}>`
3
- // (adapters/solid/src/create-portal/index.tsx) and Angular's PortalDirective/PortalOutletDirective
4
- // pair. This adapter shipped `createTunnel` and no portal in any spelling until 2026-08-20; the
5
- // two do NOT overlap (see the table at the bottom of this header), so the tunnel was never a
6
- // substitute.
1
+ // Portal — the Svelte adapter's same-surface portal: the twin of React's `createPortal`, Solid's
2
+ // `<Portal mount={…}>` and Angular's PortalDirective/PortalOutletDirective pair. Does NOT overlap
3
+ // with `createTunnel` (table at the bottom of this header) — the tunnel is not a substitute.
7
4
  //
8
5
  // WHY IT IS A COMPONENT (`<Portal mount={…}>`) AND NOT A CALL (`createPortal(content, target)`).
9
6
  // NOT for Solid's reason. Solid had to avoid the call form because Solid evaluates JSX eagerly at
@@ -0,0 +1,10 @@
1
+ import type { ShimElement } from './element';
2
+ export type IKeyframe = Readonly<Record<string, string>>;
3
+ export type IShimAnimation = {
4
+ onfinish: (() => void) | null;
5
+ effect: unknown;
6
+ readonly playState: 'idle' | 'running' | 'finished';
7
+ readonly currentTime: number;
8
+ cancel(): void;
9
+ };
10
+ export declare function animateShimElement(element: ShimElement, keyframes: readonly IKeyframe[], duration: number): IShimAnimation;
@@ -0,0 +1,80 @@
1
+ // `Element.animate()`, the subset `svelte/transition`/`svelte/animate` call: Svelte's own tick
2
+ // loop already bakes each frame's `css(t, u)` into the keyframe array, so this only replays them
3
+ // over real time (`.currentTime`/`.playState`/`.onfinish`/`.effect`/`.cancel()` contract).
4
+ import { isSymbioteNode, requestCommitFor, routeProp, } from '@symbiote-native/engine';
5
+ import { areShimAnimationsEnabled } from './animations-gate.js';
6
+ function isRecord(value) {
7
+ return typeof value === 'object' && value !== null;
8
+ }
9
+ // `opacity` parses to a number, matching a real CSSStyleDeclaration read back; every other key
10
+ // (transform, filter, ...) passes through as the authored string.
11
+ function applyKeyframe(node, style, frame) {
12
+ const next = isRecord(style) ? { ...style } : {};
13
+ for (const [key, value] of Object.entries(frame)) {
14
+ next[key] = key === 'opacity' ? Number(value) : value;
15
+ }
16
+ routeProp(node, 'style', next);
17
+ // Our own rAF loop, not a framework render - nothing else publishes this write per frame.
18
+ requestCommitFor(node);
19
+ }
20
+ class ShimAnimation {
21
+ node;
22
+ currentStyle;
23
+ keyframes;
24
+ duration;
25
+ onfinish = null;
26
+ effect = null;
27
+ playState = 'idle';
28
+ startTime = undefined;
29
+ rafId = undefined;
30
+ cancelled = false;
31
+ constructor(node, currentStyle, keyframes, duration) {
32
+ this.node = node;
33
+ this.currentStyle = currentStyle;
34
+ this.keyframes = keyframes;
35
+ this.duration = duration;
36
+ if (node === undefined || keyframes.length === 0 || duration <= 0) {
37
+ this.playState = 'finished';
38
+ // Guarded like `tick`: a same-tick `cancel()` (Svelte replacing this animation before the
39
+ // microtask runs) must not fire a handler for a transition that never got to play.
40
+ void Promise.resolve().then(() => {
41
+ if (!this.cancelled)
42
+ this.onfinish?.();
43
+ });
44
+ return;
45
+ }
46
+ this.playState = 'running';
47
+ this.rafId = requestAnimationFrame(this.tick);
48
+ }
49
+ get currentTime() {
50
+ return this.startTime === undefined ? 0 : Date.now() - this.startTime;
51
+ }
52
+ tick = (now) => {
53
+ if (this.cancelled || this.node === undefined)
54
+ return;
55
+ this.startTime ??= now;
56
+ const progress = Math.min((now - this.startTime) / this.duration, 1);
57
+ const index = Math.round(progress * (this.keyframes.length - 1));
58
+ const frame = this.keyframes[index];
59
+ if (frame !== undefined)
60
+ applyKeyframe(this.node, this.currentStyle, frame);
61
+ if (progress >= 1) {
62
+ this.playState = 'finished';
63
+ this.onfinish?.();
64
+ return;
65
+ }
66
+ this.rafId = requestAnimationFrame(this.tick);
67
+ };
68
+ cancel() {
69
+ this.cancelled = true;
70
+ this.playState = 'idle';
71
+ if (this.rafId !== undefined)
72
+ cancelAnimationFrame(this.rafId);
73
+ }
74
+ }
75
+ // A node not yet live plays no animation - the same shape Svelte's own dummy zero-duration
76
+ // animation already takes, so a caller sees a normal, already-finished Animation.
77
+ export function animateShimElement(element, keyframes, duration) {
78
+ const node = element.engineNode;
79
+ return new ShimAnimation(areShimAnimationsEnabled() && isSymbioteNode(node) ? node : undefined, element.p.style, keyframes, duration);
80
+ }
@@ -0,0 +1,2 @@
1
+ export declare function setShimAnimationsEnabled(value: boolean): void;
2
+ export declare function areShimAnimationsEnabled(): boolean;
@@ -0,0 +1,10 @@
1
+ // TODO: включить, когда API стабилизируется: `animate:flip` работает, shuffle-реордер нет
2
+ // Экран `TransitionAnimateDemo` снят с Canary, вернуть вместе с включением
3
+ // Пока выключено для потребителя, тесты включают через `setShimAnimationsEnabled`
4
+ let enabled = false;
5
+ export function setShimAnimationsEnabled(value) {
6
+ enabled = value;
7
+ }
8
+ export function areShimAnimationsEnabled() {
9
+ return enabled;
10
+ }
@@ -0,0 +1,3 @@
1
+ import type { ShimElement } from './element';
2
+ export type IComputedStyle = Readonly<Record<string, string>>;
3
+ export declare function computedStyleOf(element: ShimElement): IComputedStyle;
@@ -0,0 +1,34 @@
1
+ // The bare global `getComputedStyle`, which svelte/transition's fade/fly/scale/slide/blur call
2
+ // directly, not `document.getComputedStyle`. A Proxy, since a caller reads an arbitrary key built
3
+ // at runtime (slide derives `paddingTop`/`marginBottom` from its `axis` option).
4
+ function isRecord(value) {
5
+ return typeof value === 'object' && value !== null;
6
+ }
7
+ const DEFAULTS = {
8
+ opacity: '1',
9
+ transform: 'none',
10
+ filter: 'none',
11
+ display: 'flex',
12
+ strokeLinecap: 'butt',
13
+ strokeWidth: '0',
14
+ };
15
+ function styleValueOf(element, key) {
16
+ const style = element.p.style;
17
+ const raw = isRecord(style) ? style[key] : undefined;
18
+ if (raw !== undefined)
19
+ return typeof raw === 'number' ? `${raw}px` : String(raw);
20
+ // Two tokens, not `DEFAULTS`' single `'0px'` - `flip()` destructures this into [ox, oy] and
21
+ // divides by clientWidth/Height, so a lone token leaves oy as NaN (real default is `50% 50%`).
22
+ if (key === 'transformOrigin') {
23
+ const { width, height } = element.getBoundingClientRect();
24
+ return `${width / 2}px ${height / 2}px`;
25
+ }
26
+ return DEFAULTS[key] ?? '0px';
27
+ }
28
+ export function computedStyleOf(element) {
29
+ return new Proxy({}, {
30
+ get(_target, key) {
31
+ return typeof key === 'string' ? styleValueOf(element, key) : undefined;
32
+ },
33
+ });
34
+ }
@@ -2,6 +2,7 @@ import { ShimElement } from './element';
2
2
  import { ShimText } from './text';
3
3
  import { ShimComment } from './comment';
4
4
  import { ShimDocumentFragment } from './document-fragment';
5
+ import { type IComputedStyle } from './computed-style';
5
6
  export declare class ShimDocument {
6
7
  readonly body: ShimElement;
7
8
  createElement(tag: string, options?: {
@@ -17,7 +18,7 @@ export declare class ShimDocument {
17
18
  importNode(node: ShimText, deep: boolean): ShimText;
18
19
  importNode(node: ShimComment, deep: boolean): ShimComment;
19
20
  importNode(node: ShimDocumentFragment, deep: boolean): ShimDocumentFragment;
20
- getComputedStyle(): Record<string, never>;
21
+ getComputedStyle(element: ShimElement): IComputedStyle;
21
22
  }
22
23
  export declare function getShimDocument(): ShimDocument;
23
24
  export declare function resetShimDocumentForTests(): void;
@@ -1,14 +1,12 @@
1
- // The shim's `document` singleton — the six factories `init_operations()` and `from_tree`
2
- // require (svelte-adapter-dom-shim skill §3b), plus the two cheap stubs §4 calls for
3
- // explicitly (`document.body`, `getComputedStyle`) so a forbidden/dead path degrades to a
4
- // harmless no-op instead of a hard crash. No `<svelte:head|window|body|document>` support
5
- // beyond that stub — those tags are rejected at build time by the preprocessor (§7), not
6
- // handled here.
1
+ // The shim's `document` singleton - the six factories `init_operations()`/`from_tree` require,
2
+ // plus `document.body` (a minimal unattached node) and `getComputedStyle` (computed-style.ts).
3
+ // No `<svelte:head|window|body|document>` - rejected at build time by the preprocessor.
7
4
  import { ShimElement } from './element.js';
8
5
  import { ShimText } from './text.js';
9
6
  import { ShimComment } from './comment.js';
10
7
  import { ShimDocumentFragment } from './document-fragment.js';
11
8
  import { registerShimDocumentFactory } from './shim-node.js';
9
+ import { computedStyleOf } from './computed-style.js';
12
10
  export class ShimDocument {
13
11
  // The delegation root real Svelte events bubble toward (`dom/elements/events.js:122`).
14
12
  // Our own event names never delegate (§5c: no camelCase name matches Svelte's 23-name
@@ -39,11 +37,10 @@ export class ShimDocument {
39
37
  importNode(node, deep) {
40
38
  return node.cloneNode(deep);
41
39
  }
42
- // §4: not read by any mandatory path; stubbed so a forbidden transition (which we reject
43
- // at build time anyway, §7) degrades to an inert value rather than a hard crash if it is
44
- // ever reached some other way.
45
- getComputedStyle() {
46
- return {};
40
+ // Kept in step with the bare global patch-globals.ts installs (computed-style.ts is the one
41
+ // implementation both call).
42
+ getComputedStyle(element) {
43
+ return computedStyleOf(element);
47
44
  }
48
45
  }
49
46
  let singleton;
@@ -1,4 +1,5 @@
1
- import { type ISymbioteNode } from '@symbiote-native/engine';
1
+ import { type IDomRect, type ISymbioteNode } from '@symbiote-native/engine';
2
+ import { type IKeyframe, type IShimAnimation } from './animation';
2
3
  import { ShimNode } from './shim-node';
3
4
  export type IShimPropBag = Record<string, unknown>;
4
5
  export declare abstract class ShimElementBase extends ShimNode {
@@ -30,6 +31,19 @@ export declare class ShimElement extends ShimElementBase {
30
31
  addEventListener(name: string, handler: (event: unknown) => void): void;
31
32
  removeEventListener(name: string): void;
32
33
  cloneNode(deep?: boolean): ShimElement;
34
+ get parentElement(): ShimElement | null;
35
+ get clientWidth(): number;
36
+ get clientHeight(): number;
37
+ getBoundingClientRect(): IDomRect & {
38
+ top: number;
39
+ left: number;
40
+ right: number;
41
+ bottom: number;
42
+ };
43
+ dispatchEvent(): boolean;
44
+ animate(keyframes: readonly IKeyframe[], options: {
45
+ duration: number;
46
+ }): IShimAnimation;
33
47
  createEngineNode(): ISymbioteNode;
34
48
  onMadeLive(): void;
35
49
  }
@@ -22,9 +22,11 @@
22
22
  // A per-key diff in the setter is MANDATORY (§3g(c)), not an optimization: Svelte's
23
23
  // `set_custom_element_data` has no early-out guard and runs on every effect re-fire, so
24
24
  // without the diff every prop gets rewritten whenever any one of them changes.
25
- import { createElement, dlog, routeProp, toPublicInstance, } from '@symbiote-native/engine';
25
+ import { createElement, dlog, getBoundingClientRect, isSymbioteNode, routeProp, toPublicInstance, } from '@symbiote-native/engine';
26
26
  import { descriptorFor } from '@symbiote-native/components';
27
27
  import { normalizeSvelteClass } from '../class-value.js';
28
+ import { animateShimElement, } from './animation.js';
29
+ import { areShimAnimationsEnabled } from './animations-gate.js';
28
30
  import { BOOLEAN_PROP_NAMES, CANONICAL_BY_LOWER, CANONICAL_PROP_NAMES, } from './canonical-prop-names.js';
29
31
  import { ShimNode } from './shim-node.js';
30
32
  import { discoverStyleCacheKey } from './style-cache.js';
@@ -251,6 +253,48 @@ export class ShimElement extends ShimElementBase {
251
253
  }
252
254
  return clone;
253
255
  }
256
+ // `flip`'s `get_zoom()` walks this until `null`; without it the walk hit `undefined` and threw
257
+ // `Cannot read properties of undefined ('parentElement')`, aborting the whole reorder.
258
+ get parentElement() {
259
+ const result = this.parent instanceof ShimElement ? this.parent : null;
260
+ dlog(`parentElement tag=${this.tagName} -> ${result === null ? 'null' : result.tagName}`);
261
+ return result;
262
+ }
263
+ // `flip` divides by these for its scale factor; unset they read as `NaN`, not zero.
264
+ get clientWidth() {
265
+ return this.getBoundingClientRect().width;
266
+ }
267
+ get clientHeight() {
268
+ return this.getBoundingClientRect().height;
269
+ }
270
+ // Real DOM shape (top/left/right/bottom derived), for svelte/animate's `flip` and
271
+ // svelte/transition's `crossfade`, both of which measure before and after a reorder.
272
+ getBoundingClientRect() {
273
+ const node = this.engineNode;
274
+ const rect = areShimAnimationsEnabled() && isSymbioteNode(node)
275
+ ? getBoundingClientRect(node, false)
276
+ : undefined;
277
+ dlog(`getBoundingClientRect tag=${this.tagName} hasNode=${isSymbioteNode(node)} rect=${JSON.stringify(rect)}`);
278
+ const { x, y, width, height } = rect ?? { x: 0, y: 0, width: 0, height: 0 };
279
+ return {
280
+ x,
281
+ y,
282
+ width,
283
+ height,
284
+ top: y,
285
+ left: x,
286
+ right: x + width,
287
+ bottom: y + height,
288
+ };
289
+ }
290
+ // `transitions.js`'s `dispatch_event` fires introstart/introend/outrostart/outroend at this;
291
+ // nothing in this adapter listens, so a no-op is the correct answer, not a missing method.
292
+ dispatchEvent() {
293
+ return true;
294
+ }
295
+ animate(keyframes, options) {
296
+ return animateShimElement(this, keyframes, options.duration);
297
+ }
254
298
  // measure/measureInWindow/measureLayout/setNativeProps/focus/blur — the imperative API a
255
299
  // `bind:this` host ref hands back — ride on the engine node's prototype, so toPublicInstance is
256
300
  // the identity and this reads the same as Vue's renderer (renderer/index.ts) and React's
@@ -2,9 +2,8 @@
2
2
  // (svelte-adapter-dom-shim skill §2, §3a, §6). Must run before `mount()` (§3f) — not
3
3
  // necessarily before `svelte` is imported.
4
4
  //
5
- // Single-root only, by design (§10, decided during Svelte adapter planning 2026-08-11): one
6
- // Symbiote app = one process = one Svelte root, so `restoreGlobals()` needs no ref-counting —
7
- // `unmount()` calls it unconditionally.
5
+ // Single-root only, by design (§10): one Symbiote app = one process = one Svelte root, so
6
+ // `restoreGlobals()` needs no ref-counting — `unmount()` calls it unconditionally.
8
7
  //
9
8
  // Deliberately NOT patched, and must stay that way (§6b, §6c, §6d) — both verified against the
10
9
  // real vendored RN source (.vendors/react-native/packages/react-native/Libraries/Core/), not
@@ -30,6 +29,7 @@ import { ShimComment } from './comment.js';
30
29
  import { ShimDocumentFragment } from './document-fragment.js';
31
30
  import { ShimNode } from './shim-node.js';
32
31
  import { getShimDocument } from './document.js';
32
+ import { computedStyleOf } from './computed-style.js';
33
33
  const PATCHED_KEYS = [
34
34
  'Node',
35
35
  'Element',
@@ -41,7 +41,36 @@ const PATCHED_KEYS = [
41
41
  'document',
42
42
  'customElements',
43
43
  'HTMLMediaElement',
44
+ 'getComputedStyle',
45
+ 'CustomEvent',
44
46
  ];
47
+ // `svelte/transition`'s fade/fly/scale/slide/blur call this BARE global, not
48
+ // `document.getComputedStyle`, so it must be a real assigned property too.
49
+ function getComputedStyleGlobal(element) {
50
+ return element instanceof ShimElement
51
+ ? computedStyleOf(element)
52
+ : computedStyleOf(new ShimElement('view'));
53
+ }
54
+ // `createEventDispatcher`'s own event object; `svelte/events`'s `on()` also constructs one.
55
+ // RN installs no CustomEvent of its own, so this must be a real assigned property, matching the
56
+ // same class of trap as `customElements`/`HTMLMediaElement` below.
57
+ class FakeCustomEvent {
58
+ type;
59
+ detail;
60
+ bubbles;
61
+ cancelable;
62
+ defaultPrevented = false;
63
+ constructor(type, options) {
64
+ this.type = type;
65
+ this.detail = options?.detail;
66
+ this.bubbles = options?.bubbles ?? false;
67
+ this.cancelable = options?.cancelable ?? false;
68
+ }
69
+ preventDefault() {
70
+ if (this.cancelable)
71
+ this.defaultPrevented = true;
72
+ }
73
+ }
45
74
  // Real DOM's `CustomElementRegistry`. `set_custom_element_data` (svelte's
46
75
  // dom/elements/attributes.js) reads the BARE global `customElements` unconditionally, with no
47
76
  // `typeof` guard — an undeclared global throws ReferenceError, not just `undefined`, so this
@@ -51,20 +80,9 @@ const PATCHED_KEYS = [
51
80
  // with (unlike `navigator`/`requestAnimationFrame`, verified against .vendors/react-native —
52
81
  // see the header comment above), so patching this is safe.
53
82
  //
54
- // `get()` returns a TRUTHY stand-in, and that is the whole reason `set_custom_element_data` can be
55
- // made lossless. Its condition (attributes.js:245-265) is a ternary, not a fallback:
56
- //
57
- // setters_cache.has(nodeName) || !customElements || customElements.get(nodeName)
58
- // ? get_setters(node).includes(prop) <- a real setter decides; the value passes through
59
- // : value && typeof value === 'object' <- a HEURISTIC: an object is set as a property and
60
- // every scalar is handed on as String(value)
61
- //
62
- // Returning `undefined` — which it did until 2026-09-07 — selects the heuristic, so
63
- // `<text-input multiline={false}>` committed the STRING "false" and an object-valued prop with no
64
- // prototype setter was assigned to a plain JS property and silently lost. Anything truthy steers
65
- // it to `get_setters`, which reaches `ShimElement.prototype`'s accessors (see
66
- // `svelte-shim-element-global-must-be-an-ancestor.md` for why that walk terminates where it does).
67
- // One reader in the whole client runtime, so nothing else is affected.
83
+ // `get()` returns a TRUTHY stand-in — the whole reason `set_custom_element_data` stays lossless.
84
+ // Falsy selects attributes.js's HEURISTIC path instead of `get_setters`: it stringifies scalars
85
+ // and drops any object-valued prop with no prototype setter.
68
86
  const FAKE_CUSTOM_ELEMENT_REGISTRY = {
69
87
  get: () => ShimElement,
70
88
  };
@@ -93,6 +111,8 @@ export function patchGlobals() {
93
111
  document: g.document,
94
112
  customElements: g.customElements,
95
113
  HTMLMediaElement: g.HTMLMediaElement,
114
+ getComputedStyle: g.getComputedStyle,
115
+ CustomEvent: g.CustomEvent,
96
116
  };
97
117
  g.Node = ShimNode;
98
118
  // ShimElementBase, NOT ShimElement: `get_setters` stops AT `Element.prototype`, so pointing this
@@ -109,6 +129,8 @@ export function patchGlobals() {
109
129
  g.document = getShimDocument();
110
130
  g.customElements = FAKE_CUSTOM_ELEMENT_REGISTRY;
111
131
  g.HTMLMediaElement = FakeHTMLMediaElement;
132
+ g.getComputedStyle = getComputedStyleGlobal;
133
+ g.CustomEvent = FakeCustomEvent;
112
134
  dlog('svelte dom-shim: patchGlobals installed');
113
135
  }
114
136
  export function restoreGlobals() {
package/build/index.js CHANGED
@@ -4,11 +4,9 @@
4
4
  // All Fabric clone-on-write lives in the engine, shared cross-adapter. App code names only
5
5
  // @symbiote-native/svelte.
6
6
  //
7
- // Full component parity with React/Vue/Angular landed 2026-08-11 (svelte-adapter-dom-shim
8
- // skill §15): every `core/components` render function has a fixed tree shape, so no
9
- // `descriptorToSvelte` bridge was ever needed — each component below is hand-authored Svelte
10
- // markup mirroring its render-*.ts, reusing only the pure state/render logic. See skill §17/§18
11
- // for the one open async-exception repro and the honest cross-cutting gap list.
7
+ // Full component parity with React/Vue/Angular (svelte-adapter-dom-shim skill §15): every
8
+ // `core/components` render function has a fixed tree shape, so no `descriptorToSvelte` bridge is
9
+ // needed — each component below is hand-authored Svelte markup mirroring its render-*.ts.
12
10
  // Bare side-effect import, deliberately NOT a re-export and deliberately not beside one of the
13
11
  // same specifier: it registers the press behavior with the engine's host-behavior registry. Any
14
12
  // other shape is dropped by Metro's inlineRequires in a release build — see register.ts.
@@ -25,9 +23,8 @@ export { KeyboardAvoidingView, Modal, VirtualizedList, FlatList, VirtualizedSect
25
23
  // threaded through this barrel (parity fix, see React's index.ts lines 55-58, 89, 245-250).
26
24
  export { setImageSourceResolver } from '@symbiote-native/components';
27
25
  // Pure, framework-agnostic runtime modules — same re-export every other adapter does per
28
- // CLAUDE.md's <runtime_modules_layering>. PanResponder was missing here (present on
29
- // React/Vue's own barrels) until 2026-08-12 — a pure engine re-export, no framework glue, so
30
- // adding it is a one-line parity fix, not new adapter work.
26
+ // CLAUDE.md's <runtime_modules_layering>. A pure engine re-export with no framework glue, so
27
+ // adding one is a one-line parity fix, not new adapter work.
31
28
  export { Platform, StyleSheet, PlatformColor, DynamicColorIOS, processColor, setNativeViewConfigSource, setColorProcessor, setDeviceEventSource, PixelRatio, PanResponder, dlog, isDebug, } from '@symbiote-native/engine';
32
29
  // The two namespaces left behind by primitives that became tags, here rather than in the component
33
30
  // block above for one reason: they carry no view. `Image` gets an adapter file only because it
@@ -9,10 +9,9 @@
9
9
  // between the value graph and the node's Fabric tag, and both of those were already engine-side.
10
10
  //
11
11
  // Those three were aliases of the wrappers, and the wrappers are gone — a primitive is a tag now,
12
- // which is not a value `<Animated.X>` can call. `Animated.ScrollView` left the same way on
13
- // 2026-09-10: `bindAnimatedEvent` (node.ts, from `setEventListener`) attaches a native-driven event
14
- // on ANY host node, which was the one job a scroll wrapper still looked like it had. The whole API
15
- // is `<scroll-view style={{ opacity: v }} p={{ onScroll: Animated.event(…) }}>`.
12
+ // which is not a value `<Animated.X>` can call. `Animated.ScrollView` left the same way:
13
+ // `bindAnimatedEvent` (node.ts) attaches a native-driven event on ANY host node.
14
+ // The whole API is `<scroll-view style={{ opacity: v }} p={{ onScroll: Animated.event(…) }}>`.
16
15
  //
17
16
  // The two below REMAIN because the LIST family is tier 3: a render prop decides their output shape
18
17
  // in JS, so there is no tag for `<Animated.X>` to be an alias of.
@@ -1,11 +1,8 @@
1
1
  // svelte-adapter-dom-shim skill §16: Svelte trims only the leading/trailing whitespace of a
2
2
  // fragment's own children - never whitespace INSIDE a single Text node, and never a
3
- // whitespace-only Text node BETWEEN two siblings. Vue's compiler (and every browser) collapses
4
- // both; Svelte doesn't. Confirmed against the real compiler (2026-08-17): a Text node authored
5
- // as `Hello world, this is a\n long sentence.` compiles verbatim into
6
- // `$.text('Hello world, this is a\n long sentence.')` - the newline and indent ship into the
7
- // native text content and render as a forced line break plus stray leading spaces on device.
8
- // Reproduced live on an iOS simulator before this file existed; fixed with it registered.
3
+ // whitespace-only Text node BETWEEN two siblings. Vue's compiler (and every browser) collapse both.
4
+ // A Text node authored as `Hello world, this is a\n long sentence.` compiles verbatim, so the
5
+ // newline and indent ship into native text as a forced line break plus stray leading spaces.
9
6
  //
10
7
  // Two hazards:
11
8
  // 1. Wrapped sentence - a Text node with real content plus an embedded whitespace run
@@ -1,4 +1,4 @@
1
- // §7 of svelte-adapter-dom-shim (DECIDED 2026-08-11): the constructs that compile cleanly and
1
+ // §7 of svelte-adapter-dom-shim: the constructs that compile cleanly and
2
2
  // then silently do nothing under the DOM shim. Everything else in the original §4 table is
3
3
  // already closed by "app code can't reach it" or "the TS/Svelte compiler already errors on it for
4
4
  // free" (see the skill for the full reachability table). This preprocessor is the guard for what
@@ -127,13 +127,11 @@ export function scopedStyles() {
127
127
  // ---------------------------------------------------------------------------------------------
128
128
  // The <style> block, located WITHOUT parsing it.
129
129
  //
130
- // This is svelte's own tag regex (src/compiler/preprocess/index.js), which is what its official
131
- // `preprocess()` style hook uses to hand a block's content to a preprocessor unparsed — and the
132
- // reason `svelte-preprocess` can compile SCSS at all. Reading `parse().css` instead, as this file
133
- // did until 2026-08-20, validates the block as CSS whatever `lang` says: `<style lang="scss">$pad:
134
- // 7px;</style>` threw `css_expected_identifier` before the language table above was ever
135
- // consulted, so only SCSS that is already valid CSS (nesting) survived. A `<!-- -->` comment is an
136
- // alternative of the pattern purely so a commented-out block is skipped rather than matched.
130
+ // This is svelte's own tag regex (src/compiler/preprocess/index.js): what its official
131
+ // `preprocess()` style hook uses to hand a block's content unparsed, the reason `svelte-preprocess`
132
+ // can compile SCSS at all — `parse().css` would validate it as CSS whatever `lang` says.
133
+ // A `<!-- -->` comment is an alternative of the pattern purely so a commented-out block is
134
+ // skipped rather than matched.
137
135
  const STYLE_TAG_PATTERN = /<!--[^]*?-->|<style((?:\s+[^=>'"/\s]+=(?:"[^"]*"|'[^']*'|[^>\s]+)|\s+[^=>'"/\s]+)*\s*)(?:\/>|>([\S\s]*?)<\/style>)/g;
138
136
  const LANG_ATTRIBUTE_PATTERN = /\blang\s*=\s*(?:"([^"]*)"|'([^']*)'|([^\s>]+))/;
139
137
  function findStyleBlock(content) {
package/build/render.js CHANGED
@@ -1,7 +1,7 @@
1
1
  // Mount a Svelte app onto a Fabric surface. The native host hands us a rootTag; we create a
2
2
  // surface for it, install the DOM shim, and let stock compiled Svelte output drive it while
3
- // believing it is talking to the real DOM. Decided during Svelte adapter planning
4
- // (2026-08-11, svelte-adapter-dom-shim skill §10): single root per process, so
3
+ // believing it is talking to the real DOM.
4
+ // Single root per process (svelte-adapter-dom-shim skill §10), so
5
5
  // patchGlobals()/restoreGlobals() need no ref-counting.
6
6
  import { mount as svelteMount, unmount as svelteUnmount, } from 'svelte';
7
7
  import { createSurface, disposeRoot, dlog, reportUncaughtError, } from '@symbiote-native/engine';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@symbiote-native/svelte",
3
- "version": "3.0.3",
3
+ "version": "3.0.5",
4
4
  "description": "SymbioteNative's Svelte adapter — a DOM shim over stock compiled Svelte output driving real native iOS/Android views through the same engine as every other adapter.",
5
5
  "keywords": [
6
6
  "react-native",
@@ -73,22 +73,22 @@
73
73
  },
74
74
  "dependencies": {
75
75
  "typescript": "~6.0.0",
76
- "@symbiote-native/components": "3.1.1",
77
- "@symbiote-native/css-parser": "0.5.1"
76
+ "@symbiote-native/components": "3.1.3",
77
+ "@symbiote-native/css-parser": "0.5.2"
78
78
  },
79
79
  "peerDependencies": {
80
80
  "svelte": ">=5.56.0",
81
81
  "react-native": ">=0.86",
82
- "@symbiote-native/engine": "^1.3.0"
82
+ "@symbiote-native/engine": "^1.4.0"
83
83
  },
84
84
  "devDependencies": {
85
85
  "@vue/runtime-core": "^3.5.13",
86
86
  "react": "19.2.3",
87
87
  "svelte": "^5.56.0",
88
- "@symbiote-native/engine": "1.3.0",
89
- "@symbiote-native/react": "3.0.3",
90
- "@symbiote-native/test-utils": "0.4.3",
91
- "@symbiote-native/vue": "3.0.3"
88
+ "@symbiote-native/engine": "1.4.0",
89
+ "@symbiote-native/test-utils": "0.4.5",
90
+ "@symbiote-native/react": "3.1.0",
91
+ "@symbiote-native/vue": "3.1.0"
92
92
  },
93
93
  "scripts": {
94
94
  "typecheck": "tsc --build",