@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.
- package/build/components/virtualized-list/index.svelte +1 -2
- package/build/components/virtualized-section-list/index.svelte +3 -6
- package/build/create-portal/index.js +3 -6
- package/build/dom-shim/animation.d.ts +10 -0
- package/build/dom-shim/animation.js +80 -0
- package/build/dom-shim/animations-gate.d.ts +2 -0
- package/build/dom-shim/animations-gate.js +10 -0
- package/build/dom-shim/computed-style.d.ts +3 -0
- package/build/dom-shim/computed-style.js +34 -0
- package/build/dom-shim/document.d.ts +2 -1
- package/build/dom-shim/document.js +8 -11
- package/build/dom-shim/element.d.ts +15 -1
- package/build/dom-shim/element.js +45 -1
- package/build/dom-shim/patch-globals.js +39 -17
- package/build/index.js +5 -8
- package/build/modules/animated/index.js +3 -4
- package/build/preprocessor/collapse-text-whitespace.js +3 -6
- package/build/preprocessor/forbid-web-only-constructs.js +1 -1
- package/build/preprocessor/scoped-styles.js +5 -7
- package/build/render.js +2 -2
- package/package.json +8 -8
|
@@ -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 —
|
|
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
|
-
//
|
|
67
|
-
//
|
|
68
|
-
//
|
|
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
|
-
//
|
|
3
|
-
// (
|
|
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,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,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():
|
|
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
|
|
2
|
-
//
|
|
3
|
-
//
|
|
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
|
-
//
|
|
43
|
-
//
|
|
44
|
-
|
|
45
|
-
|
|
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
|
|
6
|
-
//
|
|
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
|
|
55
|
-
//
|
|
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
|
|
8
|
-
//
|
|
9
|
-
//
|
|
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>.
|
|
29
|
-
//
|
|
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
|
|
13
|
-
//
|
|
14
|
-
//
|
|
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)
|
|
4
|
-
//
|
|
5
|
-
//
|
|
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
|
|
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)
|
|
131
|
-
// `preprocess()` style hook uses to hand a block's content
|
|
132
|
-
//
|
|
133
|
-
//
|
|
134
|
-
//
|
|
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.
|
|
4
|
-
// (
|
|
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
|
+
"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.
|
|
77
|
-
"@symbiote-native/css-parser": "0.5.
|
|
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.
|
|
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.
|
|
89
|
-
"@symbiote-native/
|
|
90
|
-
"@symbiote-native/
|
|
91
|
-
"@symbiote-native/vue": "3.0
|
|
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",
|