@symbiote-native/svelte 3.0.1 → 3.0.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/build/components/flat-list/index.svelte +11 -0
- package/build/components/modal/index.svelte +20 -22
- package/build/components/section-list/index.svelte +2 -0
- package/build/components/virtualized-list/index.svelte +11 -1
- package/build/components/virtualized-list/virtualized-list-props.d.ts +2 -0
- package/build/components/virtualized-section-list/index.svelte +2 -0
- package/build/components/virtualized-section-list/virtualized-section-list-props.d.ts +2 -0
- package/build/dom-shim/canonical-prop-names.d.ts +1 -0
- package/build/dom-shim/canonical-prop-names.js +84 -0
- package/build/dom-shim/element.js +4 -2
- package/package.json +7 -7
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
expandRowViewability,
|
|
18
18
|
firstItemOfRow,
|
|
19
19
|
lastItemOfRow,
|
|
20
|
+
removeClippedSubviewsOrDefault,
|
|
20
21
|
rowKeyExtractor,
|
|
21
22
|
type IRow,
|
|
22
23
|
type ISeparatorProps,
|
|
@@ -24,6 +25,7 @@
|
|
|
24
25
|
type IViewableItemsChangedInfo,
|
|
25
26
|
} from '@symbiote-native/components';
|
|
26
27
|
import {
|
|
28
|
+
Platform,
|
|
27
29
|
resolveClassName,
|
|
28
30
|
type ISymbioteNode,
|
|
29
31
|
} from '@symbiote-native/engine';
|
|
@@ -38,6 +40,11 @@
|
|
|
38
40
|
|
|
39
41
|
let props: IProps<ItemT> = $props();
|
|
40
42
|
|
|
43
|
+
// FlatList.js always sends it, defaulted per platform.
|
|
44
|
+
const removeClippedSubviews = $derived(
|
|
45
|
+
removeClippedSubviewsOrDefault(props.removeClippedSubviews, Platform.OS),
|
|
46
|
+
);
|
|
47
|
+
|
|
41
48
|
// $state.raw: the inner VirtualizedList component instance (its exports), held by identity —
|
|
42
49
|
// same concern as every other handle in this adapter (Switch's hostShim, VirtualizedList's own
|
|
43
50
|
// scrollHandle over its ShimElement).
|
|
@@ -238,6 +245,8 @@
|
|
|
238
245
|
scrollEventThrottle={props.scrollEventThrottle}
|
|
239
246
|
keyboardShouldPersistTaps={props.keyboardShouldPersistTaps}
|
|
240
247
|
keyboardDismissMode={props.keyboardDismissMode}
|
|
248
|
+
{removeClippedSubviews}
|
|
249
|
+
nestedScrollEnabled={props.nestedScrollEnabled}
|
|
241
250
|
style={props.style}
|
|
242
251
|
contentContainerStyle={props.contentContainerStyle}
|
|
243
252
|
class={props.class}
|
|
@@ -286,6 +295,8 @@
|
|
|
286
295
|
scrollEventThrottle={props.scrollEventThrottle}
|
|
287
296
|
keyboardShouldPersistTaps={props.keyboardShouldPersistTaps}
|
|
288
297
|
keyboardDismissMode={props.keyboardDismissMode}
|
|
298
|
+
{removeClippedSubviews}
|
|
299
|
+
nestedScrollEnabled={props.nestedScrollEnabled}
|
|
289
300
|
style={props.style}
|
|
290
301
|
contentContainerStyle={props.contentContainerStyle}
|
|
291
302
|
class={props.class}
|
|
@@ -6,9 +6,7 @@
|
|
|
6
6
|
// @symbiote-native/components and are shared verbatim with React/Vue (core/components/src/
|
|
7
7
|
// state/modal.ts + view/render-modal.ts); Svelte supplies only the lifecycle:
|
|
8
8
|
// - $state over the keep-alive reducer (switchReducer's twin: modalReducer)
|
|
9
|
-
// - a
|
|
10
|
-
// after the DOM update, the same "one keep-alive frame survives" timing as React's
|
|
11
|
-
// useEffect / Vue's flush:'post' watch)
|
|
9
|
+
// - a $effect arming the keep-alive on show, and the native dismiss dropping it (iOS)
|
|
12
10
|
// - the descriptor bridge: renderModal() always paints the SAME fixed shape (one
|
|
13
11
|
// modal host wrapping one view container — only prop VALUES vary,
|
|
14
12
|
// never structure, per svelte-adapter-dom-shim skill §15), so rather than building a
|
|
@@ -24,41 +22,39 @@
|
|
|
24
22
|
<script lang="ts">
|
|
25
23
|
import {
|
|
26
24
|
createInitialModalState,
|
|
25
|
+
isModalVisible,
|
|
27
26
|
modalReducer,
|
|
27
|
+
modalVisibilityAction,
|
|
28
28
|
renderModal,
|
|
29
29
|
resolveAccessibilityProps,
|
|
30
30
|
shouldRenderModal,
|
|
31
31
|
} from '@symbiote-native/components';
|
|
32
|
-
import { dlog } from '@symbiote-native/engine';
|
|
32
|
+
import { dlog, Platform } from '@symbiote-native/engine';
|
|
33
33
|
import { createAttachmentsSync } from '../../runes/attachments';
|
|
34
34
|
import type { ShimElement } from '../../dom-shim';
|
|
35
35
|
|
|
36
36
|
let rawProps: IModalProps = $props();
|
|
37
37
|
|
|
38
|
-
let state = $state(createInitialModalState(rawProps.visible
|
|
38
|
+
let state = $state(createInitialModalState(isModalVisible(rawProps.visible)));
|
|
39
39
|
|
|
40
40
|
const resolved = $derived(resolveAccessibilityProps(rawProps));
|
|
41
|
-
const isVisible = $derived(resolved.visible
|
|
41
|
+
const isVisible = $derived(isModalVisible(resolved.visible));
|
|
42
42
|
const shouldRender = $derived(shouldRenderModal(isVisible, state));
|
|
43
43
|
|
|
44
|
-
//
|
|
45
|
-
//
|
|
46
|
-
// more frame (state.isRendered still true) before the NEXT render drops it. The reducer is
|
|
47
|
-
// identity-stable, so a no-op transition — including this effect's own first run on mount —
|
|
48
|
-
// triggers no extra render.
|
|
49
|
-
//
|
|
50
|
-
// Under this adapter's microtask-coalesced requestCommit() (element.ts's `p` setter), Svelte
|
|
51
|
-
// settles the whole visible->hidden cascade within the SAME microtask flush that processes the
|
|
52
|
-
// triggering prop change, so the keep-alive frame genuinely happens (a render sees
|
|
53
|
-
// isVisible=false with isRendered still true before this effect flips it) but isn't observable
|
|
54
|
-
// as its own committed Fabric frame the way React's per-render synchronous commit makes it.
|
|
44
|
+
// Arms the iOS keep-alive on show; a hide is left to the native dismiss (state/modal.ts). The
|
|
45
|
+
// reducer is identity-stable, so the mount run triggers no extra render.
|
|
55
46
|
$effect(() => {
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
isVisible ? { type: 'show' } : { type: 'hide' },
|
|
59
|
-
);
|
|
47
|
+
const action = modalVisibilityAction(isVisible);
|
|
48
|
+
if (action !== undefined) state = modalReducer(state, action);
|
|
60
49
|
});
|
|
61
50
|
|
|
51
|
+
// Modal.js: onDismiss is iOS-only — it drops the keep-alive, then tells the app.
|
|
52
|
+
function handleDismiss(): void {
|
|
53
|
+
if (Platform.OS !== 'ios') return;
|
|
54
|
+
state = modalReducer(state, { type: 'hide' });
|
|
55
|
+
rawProps.onDismiss?.();
|
|
56
|
+
}
|
|
57
|
+
|
|
62
58
|
$effect(() => {
|
|
63
59
|
if (!shouldRender) dlog('Modal hidden -> no node committed');
|
|
64
60
|
});
|
|
@@ -82,8 +78,10 @@
|
|
|
82
78
|
style,
|
|
83
79
|
class: className,
|
|
84
80
|
children: _children,
|
|
85
|
-
|
|
81
|
+
onDismiss: _onDismiss,
|
|
82
|
+
...rest
|
|
86
83
|
} = resolved;
|
|
84
|
+
const passthrough = { ...rest, onDismiss: handleDismiss };
|
|
87
85
|
|
|
88
86
|
const descriptor = renderModal({
|
|
89
87
|
visible,
|
|
@@ -106,6 +106,8 @@
|
|
|
106
106
|
scrollEventThrottle={props.scrollEventThrottle}
|
|
107
107
|
keyboardShouldPersistTaps={props.keyboardShouldPersistTaps}
|
|
108
108
|
keyboardDismissMode={props.keyboardDismissMode}
|
|
109
|
+
removeClippedSubviews={props.removeClippedSubviews}
|
|
110
|
+
nestedScrollEnabled={props.nestedScrollEnabled}
|
|
109
111
|
style={props.style}
|
|
110
112
|
contentContainerStyle={props.contentContainerStyle}
|
|
111
113
|
class={props.class}
|
|
@@ -154,6 +154,8 @@
|
|
|
154
154
|
scrollEventThrottle: props.scrollEventThrottle,
|
|
155
155
|
keyboardShouldPersistTaps: props.keyboardShouldPersistTaps,
|
|
156
156
|
keyboardDismissMode: props.keyboardDismissMode,
|
|
157
|
+
removeClippedSubviews: props.removeClippedSubviews,
|
|
158
|
+
nestedScrollEnabled: props.nestedScrollEnabled,
|
|
157
159
|
style: props.style,
|
|
158
160
|
contentContainerStyle: props.contentContainerStyle,
|
|
159
161
|
class: props.class,
|
|
@@ -437,11 +439,12 @@
|
|
|
437
439
|
};
|
|
438
440
|
});
|
|
439
441
|
|
|
442
|
+
// VirtualizedList.js: `[inversionStyle, style]` — the app's style can override the flip.
|
|
440
443
|
const resolvedStyle = $derived(
|
|
441
444
|
narrowed.inverted
|
|
442
445
|
? [
|
|
443
|
-
narrowed.style,
|
|
444
446
|
narrowed.horizontal ? INVERTED_X_STYLE : INVERTED_Y_STYLE,
|
|
447
|
+
narrowed.style,
|
|
445
448
|
]
|
|
446
449
|
: narrowed.style,
|
|
447
450
|
);
|
|
@@ -482,6 +485,13 @@
|
|
|
482
485
|
}
|
|
483
486
|
if (narrowed.keyboardDismissMode !== undefined)
|
|
484
487
|
bag.keyboardDismissMode = narrowed.keyboardDismissMode;
|
|
488
|
+
// RN's VirtualizedList spreads its props onto the ScrollView, this one included.
|
|
489
|
+
if (narrowed.removeClippedSubviews !== undefined)
|
|
490
|
+
bag.removeClippedSubviews = narrowed.removeClippedSubviews;
|
|
491
|
+
if (narrowed.nestedScrollEnabled !== undefined)
|
|
492
|
+
bag.nestedScrollEnabled = narrowed.nestedScrollEnabled;
|
|
493
|
+
// VirtualizedList.js:1111 — Android moves the scrollbar back after the `scale: -1` flip.
|
|
494
|
+
if (narrowed.inverted === true) bag.isInvertedVirtualizedList = true;
|
|
485
495
|
// `stickyHeaderIndices` is deliberately NOT forwarded. The behavior honours it by numbering the
|
|
486
496
|
// owner's own PAINT children, and this list's indices are into the DATA stream — a windowed
|
|
487
497
|
// list paints a spacer, a header and a slice, so index 3 of the data is almost never paint
|
|
@@ -62,6 +62,8 @@ export interface IVirtualizedListProps<ItemT> extends IAccessibilityProps, IAria
|
|
|
62
62
|
scrollEventThrottle?: number;
|
|
63
63
|
keyboardShouldPersistTaps?: boolean | 'always' | 'never' | 'handled';
|
|
64
64
|
keyboardDismissMode?: 'none' | 'on-drag' | 'interactive';
|
|
65
|
+
removeClippedSubviews?: boolean;
|
|
66
|
+
nestedScrollEnabled?: boolean;
|
|
65
67
|
style?: IStyleProp<IViewStyle>;
|
|
66
68
|
contentContainerStyle?: IStyleProp<IViewStyle>;
|
|
67
69
|
class?: ISvelteClassValue;
|
|
@@ -247,6 +247,8 @@
|
|
|
247
247
|
scrollEventThrottle={props.scrollEventThrottle}
|
|
248
248
|
keyboardShouldPersistTaps={props.keyboardShouldPersistTaps}
|
|
249
249
|
keyboardDismissMode={props.keyboardDismissMode}
|
|
250
|
+
removeClippedSubviews={props.removeClippedSubviews}
|
|
251
|
+
nestedScrollEnabled={props.nestedScrollEnabled}
|
|
250
252
|
style={props.style}
|
|
251
253
|
contentContainerStyle={props.contentContainerStyle}
|
|
252
254
|
class={props.class}
|
|
@@ -61,6 +61,8 @@ export interface IVirtualizedSectionListProps<ItemT> extends IAccessibilityProps
|
|
|
61
61
|
scrollEventThrottle?: number;
|
|
62
62
|
keyboardShouldPersistTaps?: boolean | 'always' | 'never' | 'handled';
|
|
63
63
|
keyboardDismissMode?: 'none' | 'on-drag' | 'interactive';
|
|
64
|
+
removeClippedSubviews?: boolean;
|
|
65
|
+
nestedScrollEnabled?: boolean;
|
|
64
66
|
style?: IStyleProp<IViewStyle>;
|
|
65
67
|
contentContainerStyle?: IStyleProp<IViewStyle>;
|
|
66
68
|
class?: ISvelteClassValue;
|
|
@@ -321,3 +321,87 @@ export const CANONICAL_PROP_NAMES = [
|
|
|
321
321
|
// collide: the derivation asserts it (`canonical-prop-names.test.ts`), because two props whose
|
|
322
322
|
// names differ only in case would make the repair a coin toss.
|
|
323
323
|
export const CANONICAL_BY_LOWER = new Map(CANONICAL_PROP_NAMES.map(name => [name.toLowerCase(), name]));
|
|
324
|
+
// The subset whose type takes a boolean and no string anywhere: for these "" can only be the HTML
|
|
325
|
+
// shorthand for true. Svelte puts a static attribute on a hyphen-less tag (`<view accessible>`)
|
|
326
|
+
// into the template as `accessible=""`, and Android's ViewManager throws on a String where it
|
|
327
|
+
// casts to Boolean. Same derivation, same guard (`canonical-prop-names.test.ts`).
|
|
328
|
+
export const BOOLEAN_PROP_NAMES = new Set([
|
|
329
|
+
'accessibilityElementsHidden',
|
|
330
|
+
'accessibilityIgnoresInvertColors',
|
|
331
|
+
'accessibilityRespondsToUserInteraction',
|
|
332
|
+
'accessibilityShowsLargeContentViewer',
|
|
333
|
+
'accessibilityViewIsModal',
|
|
334
|
+
'accessible',
|
|
335
|
+
'adjustsFontSizeToFit',
|
|
336
|
+
'allowFontScaling',
|
|
337
|
+
'allowSwipeDismissal',
|
|
338
|
+
'alwaysBounceHorizontal',
|
|
339
|
+
'alwaysBounceVertical',
|
|
340
|
+
'android_disableSound',
|
|
341
|
+
'animated',
|
|
342
|
+
'animating',
|
|
343
|
+
'aria-busy',
|
|
344
|
+
'aria-checked',
|
|
345
|
+
'aria-disabled',
|
|
346
|
+
'aria-expanded',
|
|
347
|
+
'aria-hidden',
|
|
348
|
+
'aria-modal',
|
|
349
|
+
'aria-selected',
|
|
350
|
+
'autoCorrect',
|
|
351
|
+
'autoFocus',
|
|
352
|
+
'automaticallyAdjustKeyboardInsets',
|
|
353
|
+
'blockNativeResponder',
|
|
354
|
+
'blurOnSubmit',
|
|
355
|
+
'bounces',
|
|
356
|
+
'bouncesZoom',
|
|
357
|
+
'cancelable',
|
|
358
|
+
'centerContent',
|
|
359
|
+
'collapsable',
|
|
360
|
+
'directionalLockEnabled',
|
|
361
|
+
'disableIntervalMomentum',
|
|
362
|
+
'disabled',
|
|
363
|
+
'editable',
|
|
364
|
+
'enabled',
|
|
365
|
+
'focusable',
|
|
366
|
+
'hardwareAccelerated',
|
|
367
|
+
'hasTVPreferredFocus',
|
|
368
|
+
'hidden',
|
|
369
|
+
'hidesWhenStopped',
|
|
370
|
+
'highlighted',
|
|
371
|
+
'horizontal',
|
|
372
|
+
'invertStickyHeaders',
|
|
373
|
+
'inverted',
|
|
374
|
+
'keyboardShouldPersistTaps',
|
|
375
|
+
'multiline',
|
|
376
|
+
'navigationBarTranslucent',
|
|
377
|
+
'needsOffscreenAlphaCompositing',
|
|
378
|
+
'nestedScrollEnabled',
|
|
379
|
+
'networkActivityIndicatorVisible',
|
|
380
|
+
'pagingEnabled',
|
|
381
|
+
'persistentScrollbar',
|
|
382
|
+
'pinchGestureEnabled',
|
|
383
|
+
'progressiveRenderingEnabled',
|
|
384
|
+
'readOnly',
|
|
385
|
+
'refreshing',
|
|
386
|
+
'removeClippedSubviews',
|
|
387
|
+
'renderToHardwareTextureAndroid',
|
|
388
|
+
'screenReaderFocusable',
|
|
389
|
+
'scrollEnabled',
|
|
390
|
+
'secureTextEntry',
|
|
391
|
+
'selectTextOnFocus',
|
|
392
|
+
'selectable',
|
|
393
|
+
'shouldRasterizeIOS',
|
|
394
|
+
'showSoftInputOnFocus',
|
|
395
|
+
'showsHorizontalScrollIndicator',
|
|
396
|
+
'showsVerticalScrollIndicator',
|
|
397
|
+
'snapToEnd',
|
|
398
|
+
'snapToStart',
|
|
399
|
+
'statusBarTranslucent',
|
|
400
|
+
'stickySectionHeadersEnabled',
|
|
401
|
+
'testOnly_pressed',
|
|
402
|
+
'touchSoundDisabled',
|
|
403
|
+
'translucent',
|
|
404
|
+
'transparent',
|
|
405
|
+
'useForeground',
|
|
406
|
+
'visible',
|
|
407
|
+
]);
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
import { createElement, dlog, routeProp, toPublicInstance, } from '@symbiote-native/engine';
|
|
26
26
|
import { descriptorFor } from '@symbiote-native/components';
|
|
27
27
|
import { normalizeSvelteClass } from '../class-value.js';
|
|
28
|
-
import { CANONICAL_BY_LOWER, CANONICAL_PROP_NAMES, } from './canonical-prop-names.js';
|
|
28
|
+
import { BOOLEAN_PROP_NAMES, CANONICAL_BY_LOWER, CANONICAL_PROP_NAMES, } from './canonical-prop-names.js';
|
|
29
29
|
import { ShimNode } from './shim-node.js';
|
|
30
30
|
import { discoverStyleCacheKey } from './style-cache.js';
|
|
31
31
|
// The bag a not-yet-live element is diffed against in onMadeLive. A module constant rather than a
|
|
@@ -144,7 +144,9 @@ export class ShimElement extends ShimElementBase {
|
|
|
144
144
|
// one we do not know) is handed on untouched. See `canonical-prop-names.ts`.
|
|
145
145
|
setAttribute(name, value) {
|
|
146
146
|
(this.attributes ??= new Map()).set(name, value);
|
|
147
|
-
|
|
147
|
+
const canonical = CANONICAL_BY_LOWER.get(name) ?? name;
|
|
148
|
+
// `<view accessible>` arrives as "" — the HTML shorthand for true on a boolean prop.
|
|
149
|
+
this.writeBagKey(canonical, value === '' && BOOLEAN_PROP_NAMES.has(canonical) ? true : value);
|
|
148
150
|
}
|
|
149
151
|
getAttribute(name) {
|
|
150
152
|
return this.attributes?.get(name) ?? null;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@symbiote-native/svelte",
|
|
3
|
-
"version": "3.0.
|
|
3
|
+
"version": "3.0.2",
|
|
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.0
|
|
76
|
+
"@symbiote-native/components": "3.1.0",
|
|
77
77
|
"@symbiote-native/css-parser": "0.5.0"
|
|
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.3.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.3.0",
|
|
89
|
+
"@symbiote-native/test-utils": "0.4.2",
|
|
90
|
+
"@symbiote-native/react": "3.0.2",
|
|
91
|
+
"@symbiote-native/vue": "3.0.2"
|
|
92
92
|
},
|
|
93
93
|
"scripts": {
|
|
94
94
|
"typecheck": "tsc --build",
|