@symbiote-native/vue 3.3.0 → 3.4.0

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.
Files changed (49) hide show
  1. package/build/components/flat-list/index.d.ts +4 -3
  2. package/build/components/flat-list/index.js +18 -21
  3. package/build/components/input-accessory-view-props.d.ts +4 -4
  4. package/build/components/keyboard-avoiding-view.d.ts +11 -3
  5. package/build/components/keyboard-avoiding-view.js +38 -69
  6. package/build/components/layout-conformance-props.d.ts +4 -0
  7. package/build/components/layout-conformance-props.js +1 -0
  8. package/build/components/modal/index.d.ts +18 -5
  9. package/build/components/modal/index.js +53 -24
  10. package/build/components/refresh-control-props.d.ts +7 -7
  11. package/build/components/scroll-view/scroll-view-props.d.ts +8 -5
  12. package/build/components/section-list/index.d.ts +1 -1
  13. package/build/components/section-list/index.js +2 -6
  14. package/build/components/text-props.d.ts +3 -3
  15. package/build/components/touchable-props.d.ts +6 -6
  16. package/build/components/virtualized-list/index.d.ts +5 -75
  17. package/build/components/virtualized-list/index.js +130 -558
  18. package/build/components/virtualized-list/list-children.d.ts +18 -0
  19. package/build/components/virtualized-list/list-children.js +81 -0
  20. package/build/components/virtualized-list/list-elements.d.ts +7 -0
  21. package/build/components/virtualized-list/list-elements.js +29 -0
  22. package/build/components/virtualized-list/list-scroll-props.d.ts +20 -0
  23. package/build/components/virtualized-list/list-scroll-props.js +9 -0
  24. package/build/components/virtualized-list/narrow-props.d.ts +79 -0
  25. package/build/components/virtualized-list/narrow-props.js +89 -0
  26. package/build/components/virtualized-list/nested-scope.d.ts +3 -0
  27. package/build/components/virtualized-list/nested-scope.js +12 -0
  28. package/build/components/virtualized-list/user-handlers.d.ts +3 -0
  29. package/build/components/virtualized-list/user-handlers.js +24 -0
  30. package/build/components/virtualized-list/virtualized-list-props.d.ts +82 -0
  31. package/build/components/virtualized-list/virtualized-list-props.js +41 -0
  32. package/build/components/virtualized-section-list/index.d.ts +20 -5
  33. package/build/components/virtualized-section-list/index.js +81 -95
  34. package/build/components/virtualized-section-list/section-item-cell.d.ts +18 -0
  35. package/build/components/virtualized-section-list/section-item-cell.js +52 -0
  36. package/build/composables/use-animated-value.d.ts +7 -0
  37. package/build/composables/use-animated-value.js +14 -0
  38. package/build/composables/use-color-scheme.d.ts +1 -1
  39. package/build/create-transition/index.d.ts +2 -2
  40. package/build/create-transition/index.js +1 -1
  41. package/build/index.d.ts +13 -4
  42. package/build/index.js +16 -31
  43. package/build/modules/animated/index.d.ts +54 -41
  44. package/build/modules/animated/index.js +2 -26
  45. package/build/modules/app-registry/index.d.ts +3 -3
  46. package/build/modules/app-registry/index.js +2 -2
  47. package/build/modules/status-bar.js +22 -24
  48. package/metro-vue-transformer.cjs +165 -224
  49. package/package.json +6 -6
@@ -1,15 +1,16 @@
1
- import { type VNode } from '@vue/runtime-core';
1
+ import { type Component, type VNode } from '@vue/runtime-core';
2
2
  import { type ISeparatorProps, type ISeparators, type IViewabilityConfigCallbackPair, type IViewableItemsChangedInfo, type IVirtualizedListHandle } from '@symbiote-native/components';
3
3
  import { type IStyleProp, type IViewStyle } from '@symbiote-native/engine';
4
4
  export type IFlatListHandle = IVirtualizedListHandle;
5
- export interface IFlatListProps<ItemT> {
5
+ export type IFlatListProps<ItemT> = {
6
6
  data: readonly ItemT[];
7
7
  keyExtractor?: (item: ItemT, index: number) => string;
8
+ listItemComponent?: Component;
8
9
  numColumns?: number;
9
10
  columnWrapperStyle?: IStyleProp<IViewStyle> | string;
10
11
  viewabilityConfigCallbackPairs?: IViewabilityConfigCallbackPair<ItemT>[];
11
12
  [key: string]: unknown;
12
- }
13
+ };
13
14
  export type IFlatListSlots<ItemT> = {
14
15
  item: (info: {
15
16
  item: ItemT;
@@ -10,9 +10,10 @@
10
10
  // $attrs onto the inner VirtualizedList. The inner list still GATES RefreshControl + viewability
11
11
  // on callback presence, so each emit bridge is wired ONLY when the consumer actually listens.
12
12
  import { defineComponent, getCurrentInstance, h, shallowRef, } from '@vue/runtime-core';
13
- import { SINGLE_COLUMN, chunkIntoRows, expandRowViewability, firstItemOfRow, lastItemOfRow, removeClippedSubviewsOrDefault, rowKeyExtractor, } from '@symbiote-native/components';
13
+ import { SINGLE_COLUMN, arrayLikeLength, chunkIntoRows, expandRowViewability, firstItemOfRow, lastItemOfRow, removeClippedSubviewsOrDefault, routeScrollHandle, rowKeyExtractor, } from '@symbiote-native/components';
14
14
  import { Platform, dlog, resolveClassName, } from '@symbiote-native/engine';
15
15
  import { VirtualizedList } from '../virtualized-list/index.js';
16
+ import { renderCellContent } from '../virtualized-list/list-elements.js';
16
17
  import { normalizeVueAttrs } from '../../utils/normalize-attrs.js';
17
18
  // VirtualizedList's generic construct signature can't be resolved by h()'s overloads, so drive
18
19
  // it through a loose functional-component handle (the ItemT surface is proven at the typed
@@ -23,6 +24,7 @@ const VirtualizedListHost = VirtualizedList;
23
24
  const PROP_KEYS = [
24
25
  'data',
25
26
  'keyExtractor',
27
+ 'listItemComponent',
26
28
  'numColumns',
27
29
  'columnWrapperStyle',
28
30
  'viewabilityConfigCallbackPairs',
@@ -40,6 +42,12 @@ function isRecord(value) {
40
42
  function isVirtualizedListHandle(value) {
41
43
  return isRecord(value) && typeof value.scrollToOffset === 'function';
42
44
  }
45
+ // A bare string is a registered class name, an object or array is a style, anything else is none
46
+ function wrapperStyleOf(style) {
47
+ if (typeof style === 'string')
48
+ return resolveClassName(style);
49
+ return isRecord(style) || Array.isArray(style) ? style : undefined;
50
+ }
43
51
  // The handle FlatList exposes delegates to the inner VirtualizedList's handle (Vue resolves a
44
52
  // parent ref to the exposed object, so the wrapper must re-expose rather than forward the ref).
45
53
  function buildDelegateHandle(getInner) {
@@ -48,12 +56,7 @@ function buildDelegateHandle(getInner) {
48
56
  scrollToIndex: params => getInner()?.scrollToIndex(params),
49
57
  scrollToItem: params => getInner()?.scrollToItem(params),
50
58
  scrollToEnd: params => getInner()?.scrollToEnd(params),
51
- flashScrollIndicators: () => getInner()?.flashScrollIndicators(),
52
- getNativeScrollRef: () => getInner()?.getNativeScrollRef() ?? null,
53
- getScrollableNode: () => getInner()?.getScrollableNode() ?? null,
54
- getScrollResponder: () => getInner()?.getScrollResponder() ?? null,
55
- getScrollNode: () => getInner()?.getScrollNode() ?? null,
56
- recordInteraction: () => getInner()?.recordInteraction(),
59
+ ...routeScrollHandle(getInner),
57
60
  };
58
61
  }
59
62
  export const FlatList = defineComponent((props, { attrs, expose, emit, slots, }) => {
@@ -70,10 +73,10 @@ export const FlatList = defineComponent((props, { attrs, expose, emit, slots, })
70
73
  return vnodeProps != null && typeof vnodeProps[onName] === 'function';
71
74
  };
72
75
  return () => {
73
- const data = Array.isArray(props.data)
74
- ? props.data
75
- : [];
76
+ // RN takes any array-like, and a null or a non-list as an empty one
77
+ const data = props.data;
76
78
  const keyExtractor = props.keyExtractor;
79
+ const listItemComponent = props.listItemComponent;
77
80
  const numColumns = typeof props.numColumns === 'number' ? props.numColumns : SINGLE_COLUMN;
78
81
  const viewabilityPairs = props.viewabilityConfigCallbackPairs;
79
82
  const chromeSlots = {
@@ -87,7 +90,7 @@ export const FlatList = defineComponent((props, { attrs, expose, emit, slots, })
87
90
  ...normalizedAttrs,
88
91
  removeClippedSubviews: removeClippedSubviewsOrDefault(normalizedAttrs.removeClippedSubviews, Platform.OS),
89
92
  };
90
- dlog(`Vue FlatList over ${data.length} items, ${numColumns} column(s)`);
93
+ dlog(`Vue FlatList over ${arrayLikeLength(data)} items, ${numColumns} column(s)`);
91
94
  const endReached = listens('onEndReached')
92
95
  ? (info) => emit('endReached', info)
93
96
  : undefined;
@@ -110,8 +113,9 @@ export const FlatList = defineComponent((props, { attrs, expose, emit, slots, })
110
113
  ref: setInner,
111
114
  data,
112
115
  getItem: (_source, index) => data[index],
113
- getItemCount: () => data.length,
116
+ getItemCount: () => arrayLikeLength(data),
114
117
  keyExtractor,
118
+ listItemComponent: props.listItemComponent,
115
119
  onEndReached: endReached,
116
120
  onStartReached: startReached,
117
121
  onRefresh: refresh,
@@ -125,12 +129,7 @@ export const FlatList = defineComponent((props, { attrs, expose, emit, slots, })
125
129
  const rows = chunkIntoRows(data, numColumns);
126
130
  const rowStyle = [
127
131
  { flexDirection: 'row' },
128
- typeof props.columnWrapperStyle === 'string'
129
- ? resolveClassName(props.columnWrapperStyle)
130
- : isRecord(props.columnWrapperStyle) ||
131
- Array.isArray(props.columnWrapperStyle)
132
- ? props.columnWrapperStyle
133
- : undefined,
132
+ wrapperStyleOf(props.columnWrapperStyle),
134
133
  ];
135
134
  const rowItemSlot = (info) => {
136
135
  const cells = info.item.items.map((item, column) => {
@@ -138,9 +137,7 @@ export const FlatList = defineComponent((props, { attrs, expose, emit, slots, })
138
137
  const key = keyExtractor ? keyExtractor(item, index) : String(index);
139
138
  // The row IS the virtualized cell, so every item in it shares the row's separators
140
139
  // handle (the divider sits between rows, not columns), like RN's multi-column FlatList.
141
- return h('view', { key, style: { flex: 1 } }, slots.item !== undefined
142
- ? slots.item({ item, index, separators: info.separators })
143
- : []);
140
+ return h('view', { key, style: { flex: 1 } }, renderCellContent({ renderItem: slots.item, listItemComponent }, { item, index, separators: info.separators }) ?? []);
144
141
  });
145
142
  return [h('view', { style: rowStyle }, cells)];
146
143
  };
@@ -1,8 +1,8 @@
1
1
  import type { IAccessibilityProps, IAriaProps } from '@symbiote-native/components';
2
- import type { IClassNameValue, IStyleProp, IViewStyle } from '@symbiote-native/engine';
3
- export interface IInputAccessoryViewProps extends IAccessibilityProps, IAriaProps {
2
+ import type { IClassNameValue, IColorValue, IStyleProp, IViewStyle } from '@symbiote-native/engine';
3
+ export type IInputAccessoryViewProps = IAccessibilityProps & IAriaProps & {
4
4
  nativeID?: string;
5
- backgroundColor?: string;
5
+ backgroundColor?: IColorValue;
6
6
  style?: IStyleProp<IViewStyle>;
7
7
  class?: IClassNameValue;
8
- }
8
+ };
@@ -1,6 +1,6 @@
1
1
  import { type ISymbioteEvent, type IClassNameValue, type IStyleProp, type IViewStyle } from '@symbiote-native/engine';
2
2
  import { type IAccessibilityProps, type IAriaProps, type IKeyboardAvoidingBehavior } from '@symbiote-native/components';
3
- export interface IKeyboardAvoidingViewProps extends IAccessibilityProps, IAriaProps {
3
+ export type IKeyboardAvoidingViewProps = IAccessibilityProps & IAriaProps & {
4
4
  behavior?: IKeyboardAvoidingBehavior;
5
5
  enabled?: boolean;
6
6
  keyboardVerticalOffset?: number;
@@ -8,11 +8,19 @@ export interface IKeyboardAvoidingViewProps extends IAccessibilityProps, IAriaPr
8
8
  style?: IStyleProp<IViewStyle>;
9
9
  class?: IClassNameValue;
10
10
  testID?: string;
11
- }
11
+ };
12
12
  export type IKeyboardAvoidingViewEmits = {
13
13
  layout: (event: ISymbioteEvent) => boolean;
14
14
  };
15
15
  export type { IKeyboardAvoidingBehavior } from '@symbiote-native/components';
16
- export declare const KeyboardAvoidingView: import("vue").DefineSetupFnComponent<IKeyboardAvoidingViewProps, IKeyboardAvoidingViewEmits, {}, IKeyboardAvoidingViewProps & {
16
+ export declare const KeyboardAvoidingView: import("vue").DefineSetupFnComponent<IKeyboardAvoidingViewProps, IKeyboardAvoidingViewEmits, {}, IAccessibilityProps & IAriaProps & {
17
+ behavior?: IKeyboardAvoidingBehavior;
18
+ enabled?: boolean;
19
+ keyboardVerticalOffset?: number;
20
+ contentContainerStyle?: IStyleProp<IViewStyle>;
21
+ style?: IStyleProp<IViewStyle>;
22
+ class?: IClassNameValue;
23
+ testID?: string;
24
+ } & {
17
25
  onLayout?: ((event: ISymbioteEvent) => any) | undefined;
18
26
  }, import("vue").PublicProps>;
@@ -1,15 +1,8 @@
1
- // KeyboardAvoidingView: the Vue lifecycle half. The inset math + the behavior->style/structure
2
- // decision live in @symbiote-native/components (render-keyboard-avoiding-view), shared verbatim
3
- // with React; Vue supplies only the reactivity: a ref holds the inset, onMounted subscribes to
4
- // this host's two keyboard notifications (keyboardAvoidingEventNamesFor — will* on iOS, did* on
5
- // Android, and never changeFrame), onUnmounted tears the subscriptions down, and onLayout
6
- // measures the wrapper frame that feeds the next event's inset.
7
- //
8
- // Inputs arrive as attrs (untyped), so each is narrowed with a runtime guard, never a cast.
9
- // rawAttrs runs through normalizeVueAttrs so a template `:keyboard-vertical-offset` resolves.
1
+ // KeyboardAvoidingView: the Vue lifecycle half over the shared `createKeyboardAvoidingModel`
2
+ // Inputs arrive as attrs and are narrowed with runtime guards, never a cast
10
3
  import { defineComponent, h, ref, onMounted, onUnmounted, } from '@vue/runtime-core';
11
- import { Keyboard, Platform, dlog, } from '@symbiote-native/engine';
12
- import { computeInset, configureKeyboardAvoidingAnimation, keyboardAvoidingEventNamesFor, readKeyboardAnimationTiming, readKeyboardFrame, readPrefersCrossFadeTransitions, readLayoutFrame, resolveKeyboardAvoidingLayout, resolveAccessibilityProps, DEFAULT_VERTICAL_OFFSET, } from '@symbiote-native/components';
4
+ import { Keyboard, Platform, } from '@symbiote-native/engine';
5
+ import { createKeyboardAvoidingModel, keyboardAvoidingEventNamesFor, readPrefersCrossFadeTransitions, resolveKeyboardAvoidingLayout, resolveAccessibilityProps, DEFAULT_VERTICAL_OFFSET, } from '@symbiote-native/components';
13
6
  import { normalizeVueAttrs } from '../utils/normalize-attrs.js';
14
7
  function isStyleProp(value) {
15
8
  return typeof value === 'object' && value !== null;
@@ -35,55 +28,31 @@ function forwardAttrs(attrs) {
35
28
  }
36
29
  return result;
37
30
  }
38
- export const KeyboardAvoidingView = defineComponent((_props, { attrs: rawAttrs, slots, emit }) => {
39
- const inset = ref(0);
40
- // Mutable, not reactive: changing the measured frame alone shouldn't re-render; it feeds the
41
- // next keyboard event's inset math.
42
- let frame;
43
- let initialHeight;
44
- // A device accessibility setting, read once per mount. Deliberately a plain variable, not a
45
- // ref: it cannot change during a session, so nothing should re-render when it resolves.
31
+ // The model with its keyboard subscriptions, kept apart so the setup reads as the render
32
+ function useKeyboardModel(currentAttrs, setInset) {
33
+ // A device setting read once per mount, a plain variable since nothing should re-render for it
46
34
  let prefersCrossFadeTransitions = false;
47
- // Read at EVENT time rather than captured once: attrs is live, and both the offset and the
48
- // behavior feed every keyboard notification's inset math.
49
- const currentAttrs = () => normalizeVueAttrs(rawAttrs);
50
- const verticalOffset = () => {
51
- const offset = currentAttrs().keyboardVerticalOffset;
52
- return typeof offset === 'number' ? offset : DEFAULT_VERTICAL_OFFSET;
53
- };
54
- const onShow = (payload) => {
55
- const keyboard = readKeyboardFrame(payload);
56
- const previousInset = inset.value;
57
- const next = computeInset(frame, keyboard, verticalOffset(), {
58
- behavior: asBehavior(currentAttrs().behavior),
59
- // The inset CURRENTLY applied (RN's this.state.bottom), read live off the reactive cell:
60
- // in 'height' mode the wrapper is shrunk by it, so the next onLayout under-reports the
61
- // frame by exactly that much and core adds it back.
62
- previousInset,
63
- prefersCrossFadeTransitions,
64
- });
65
- // RN's `_updateBottomIfNecessary` skips the animation when the inset did not change.
66
- if (next !== previousInset)
67
- configureKeyboardAvoidingAnimation(readKeyboardAnimationTiming(payload), currentAttrs().enabled !== false);
68
- dlog(`KeyboardAvoidingView show -> inset ${next}`);
69
- inset.value = next;
70
- };
71
- const onHide = () => {
72
- dlog('KeyboardAvoidingView hide -> inset 0');
73
- inset.value = 0;
74
- };
35
+ const model = createKeyboardAvoidingModel({
36
+ options: () => {
37
+ const attrs = currentAttrs();
38
+ const offset = attrs.keyboardVerticalOffset;
39
+ return {
40
+ behavior: asBehavior(attrs.behavior),
41
+ enabled: attrs.enabled !== false,
42
+ keyboardVerticalOffset: typeof offset === 'number' ? offset : DEFAULT_VERTICAL_OFFSET,
43
+ };
44
+ },
45
+ setInset,
46
+ prefersCrossFade: () => prefersCrossFadeTransitions,
47
+ });
75
48
  let subscriptions = [];
76
49
  onMounted(() => {
77
- // TWO notifications, per host: iOS takes the will* pair so the view rides up with the
78
- // keyboard animation, Android the did* pair. changeFrame is deliberately absent — with an
79
- // undocked/split/floating iOS keyboard it fires BEFORE the hide, mid-dismissal.
80
50
  const events = keyboardAvoidingEventNamesFor(Platform.OS);
81
51
  subscriptions = [
82
- Keyboard.addListener(events.show, onShow),
83
- Keyboard.addListener(events.hide, onHide),
52
+ Keyboard.addListener(events.show, model.keyboardShown),
53
+ Keyboard.addListener(events.hide, model.keyboardHidden),
84
54
  ];
85
- // Nobody awaits this, so it goes through the core wrapper, which answers false on a failed
86
- // native read instead of leaving an unhandled rejection.
55
+ // Nobody awaits this, the core wrapper answers false on a failed native read
87
56
  void readPrefersCrossFadeTransitions().then(enabled => {
88
57
  prefersCrossFadeTransitions = enabled;
89
58
  });
@@ -93,38 +62,38 @@ export const KeyboardAvoidingView = defineComponent((_props, { attrs: rawAttrs,
93
62
  subscription.remove();
94
63
  subscriptions = [];
95
64
  });
65
+ return model;
66
+ }
67
+ export const KeyboardAvoidingView = defineComponent((_props, { attrs: rawAttrs, slots, emit }) => {
68
+ const inset = ref(0);
69
+ // Read at event time, the attrs are live
70
+ const currentAttrs = () => normalizeVueAttrs(rawAttrs);
71
+ const model = useKeyboardModel(currentAttrs, value => {
72
+ inset.value = value;
73
+ });
96
74
  const handleLayout = (event) => {
97
- const measured = readLayoutFrame(event.nativeEvent.layout);
98
- if (measured !== undefined) {
99
- frame = measured;
100
- if (initialHeight === undefined)
101
- initialHeight = measured.height;
102
- }
75
+ model.laidOut(event.nativeEvent.layout);
103
76
  emit('layout', event);
104
77
  };
105
78
  return () => {
106
79
  const attrs = currentAttrs();
107
- const behavior = asBehavior(attrs.behavior);
108
- // RN gates every inset on `enabled ?? true`; only an explicit `false` disables.
80
+ // RN gates every inset on `enabled ?? true`, only an explicit `false` disables
109
81
  const isEnabled = attrs.enabled !== false;
110
- const effectiveInset = isEnabled ? inset.value : 0;
111
82
  const layout = resolveKeyboardAvoidingLayout({
112
- behavior,
113
- effectiveInset,
114
- initialHeight,
83
+ behavior: asBehavior(attrs.behavior),
84
+ effectiveInset: isEnabled ? inset.value : 0,
85
+ initialHeight: model.initialHeight(),
115
86
  style: isStyleProp(attrs.style) ? attrs.style : undefined,
116
87
  contentContainerStyle: isStyleProp(attrs.contentContainerStyle)
117
88
  ? attrs.contentContainerStyle
118
89
  : undefined,
119
90
  });
120
- const childNodes = slots.default !== undefined ? slots.default() : undefined;
91
+ const childNodes = slots.default?.();
121
92
  const wrapperProps = {
122
93
  ...resolveAccessibilityProps(forwardAttrs(attrs)),
123
94
  style: layout.wrapperStyle,
124
95
  onLayout: handleLayout,
125
96
  };
126
- // 'nested' pushes the children in an inner view by `bottom: inset`; the wrapper modes
127
- // adjust the single wrapper directly.
128
97
  if (layout.kind === 'nested') {
129
98
  return h('view', wrapperProps, [
130
99
  h('view', { style: layout.innerStyle }, childNodes),
@@ -0,0 +1,4 @@
1
+ import type { ILayoutConformanceMode } from '@symbiote-native/components';
2
+ export type ILayoutConformanceProps = {
3
+ mode: ILayoutConformanceMode;
4
+ };
@@ -0,0 +1 @@
1
+ export {};
@@ -1,10 +1,10 @@
1
1
  import { type IAccessibilityProps, type IAriaProps, type IModalAnimationType, type IModalOrientation, type IModalPresentationStyle } from '@symbiote-native/components';
2
- import { type IClassNameValue, type IStyleProp, type ISymbioteEvent, type IViewStyle } from '@symbiote-native/engine';
2
+ import { type IClassNameValue, type IColorValue, type IStyleProp, type ISymbioteEvent, type IViewStyle } from '@symbiote-native/engine';
3
3
  export type { IModalAnimationType, IModalPresentationStyle, IModalOrientation, IModalOrientationChangeEvent, } from '@symbiote-native/components';
4
- export interface IModalProps extends IAccessibilityProps, IAriaProps {
4
+ export type IModalProps = IAccessibilityProps & IAriaProps & {
5
5
  visible?: boolean;
6
6
  transparent?: boolean;
7
- backdropColor?: string;
7
+ backdropColor?: IColorValue;
8
8
  animationType?: IModalAnimationType;
9
9
  presentationStyle?: IModalPresentationStyle;
10
10
  supportedOrientations?: ReadonlyArray<IModalOrientation>;
@@ -14,14 +14,27 @@ export interface IModalProps extends IAccessibilityProps, IAriaProps {
14
14
  allowSwipeDismissal?: boolean;
15
15
  style?: IStyleProp<IViewStyle>;
16
16
  class?: IClassNameValue;
17
- }
17
+ };
18
18
  export type IModalEmits = {
19
19
  show: () => boolean;
20
20
  dismiss: () => boolean;
21
21
  requestClose: () => boolean;
22
22
  orientationChange: (event: ISymbioteEvent) => boolean;
23
23
  };
24
- export declare const Modal: import("vue").DefineSetupFnComponent<IModalProps, IModalEmits, {}, IModalProps & {
24
+ export declare const Modal: import("vue").DefineSetupFnComponent<IModalProps, IModalEmits, {}, IAccessibilityProps & IAriaProps & {
25
+ visible?: boolean;
26
+ transparent?: boolean;
27
+ backdropColor?: IColorValue;
28
+ animationType?: IModalAnimationType;
29
+ presentationStyle?: IModalPresentationStyle;
30
+ supportedOrientations?: ReadonlyArray<IModalOrientation>;
31
+ hardwareAccelerated?: boolean;
32
+ statusBarTranslucent?: boolean;
33
+ navigationBarTranslucent?: boolean;
34
+ allowSwipeDismissal?: boolean;
35
+ style?: IStyleProp<IViewStyle>;
36
+ class?: IClassNameValue;
37
+ } & {
25
38
  onShow?: (() => any) | undefined;
26
39
  onDismiss?: (() => any) | undefined;
27
40
  onRequestClose?: (() => any) | undefined;
@@ -10,16 +10,14 @@
10
10
  // Inputs arrive as attrs (untyped), each narrowed with a runtime guard rather than a cast. The
11
11
  // events (onShow/onDismiss/onRequestClose/onOrientationChange) are real ViewConfig DirectEvents,
12
12
  // so, unlike Switch's pure-JS onValueChange, they forward to the host raw via passthrough.
13
- import { defineComponent, h, ref, watch } from '@vue/runtime-core';
14
- import { createInitialModalState, isModalVisible, modalReducer, modalVisibilityAction, renderModal, resolveAccessibilityProps, shouldRenderModal, } from '@symbiote-native/components';
15
- import { dlog, Platform, } from '@symbiote-native/engine';
13
+ import { defineComponent, getCurrentInstance, h, ref, watch, watchEffect, } from '@vue/runtime-core';
14
+ import { createInitialModalState, isModalVisible, modalReducer, modalVisibilityAction, renderModal, resolveAccessibilityProps, shouldRenderModal, warnAboutModalProps, } from '@symbiote-native/components';
15
+ import { dlog, isProcessableColor, Platform, } from '@symbiote-native/engine';
16
16
  import { normalizeVueAttrs } from '../../utils/normalize-attrs.js';
17
+ import { provideVirtualizedListScope } from '../virtualized-list/nested-scope.js';
17
18
  function asBoolean(value) {
18
19
  return typeof value === 'boolean' ? value : undefined;
19
20
  }
20
- function asString(value) {
21
- return typeof value === 'string' ? value : undefined;
22
- }
23
21
  function asAnimationType(value) {
24
22
  return value === 'none' || value === 'slide' || value === 'fade'
25
23
  ? value
@@ -78,15 +76,59 @@ function forwardAttrs(attrs) {
78
76
  }
79
77
  return result;
80
78
  }
81
- export const Modal = defineComponent((_props, { attrs: rawAttrs, slots, emit }) => {
79
+ // Arms the iOS keep-alive on show; a hide is left to the native dismiss (state/modal.ts). The
80
+ // reducer is identity-stable, so a no-op transition triggers no extra render.
81
+ function useKeepAlive(rawAttrs) {
82
82
  const state = ref(createInitialModalState(isModalVisible(rawAttrs.visible)));
83
- // Arms the iOS keep-alive on show; a hide is left to the native dismiss (state/modal.ts). The
84
- // reducer is identity-stable, so a no-op transition triggers no extra render.
85
83
  watch(() => isModalVisible(rawAttrs.visible), isVisible => {
86
84
  const action = modalVisibilityAction(isVisible);
87
85
  if (action !== undefined)
88
86
  state.value = modalReducer(state.value, action);
89
87
  }, { flush: 'post' });
88
+ return state;
89
+ }
90
+ // The attrs the render and the dev warnings both read
91
+ function modalFlagsOf(attrs) {
92
+ return {
93
+ transparent: asBoolean(attrs.transparent),
94
+ presentationStyle: asPresentationStyle(attrs.presentationStyle),
95
+ statusBarTranslucent: asBoolean(attrs.statusBarTranslucent),
96
+ navigationBarTranslucent: asBoolean(attrs.navigationBarTranslucent),
97
+ allowSwipeDismissal: asBoolean(attrs.allowSwipeDismissal),
98
+ };
99
+ }
100
+ // Kept apart so the setup reads as lifecycle: the attrs narrowed into what `renderModal` takes
101
+ function modalRootOf(attrs) {
102
+ return renderModal({
103
+ ...modalFlagsOf(attrs),
104
+ visible: asBoolean(attrs.visible),
105
+ backdropColor: isProcessableColor(attrs.backdropColor)
106
+ ? attrs.backdropColor
107
+ : undefined,
108
+ animationType: asAnimationType(attrs.animationType),
109
+ supportedOrientations: asSupportedOrientations(attrs.supportedOrientations),
110
+ hardwareAccelerated: asBoolean(attrs.hardwareAccelerated),
111
+ style: isStyleProp(attrs.style) ? attrs.style : undefined,
112
+ passthrough: resolveAccessibilityProps(forwardAttrs(attrs)),
113
+ });
114
+ }
115
+ // RN checks on mount and on every update, a dev build only
116
+ // The `requestClose` emit is stripped from the attrs, so the vnode props tell if it is listened to
117
+ function useModalWarnings(rawAttrs) {
118
+ const listeners = getCurrentInstance()?.vnode.props;
119
+ watchEffect(() => {
120
+ const attrs = normalizeVueAttrs(rawAttrs);
121
+ warnAboutModalProps({
122
+ ...modalFlagsOf(attrs),
123
+ onRequestClose: typeof listeners?.onRequestClose === 'function',
124
+ });
125
+ });
126
+ }
127
+ export const Modal = defineComponent((_props, { attrs: rawAttrs, slots, emit }) => {
128
+ // The content sits outside the list's scroll, so a list in it is not nested in that list
129
+ provideVirtualizedListScope(null);
130
+ useModalWarnings(rawAttrs);
131
+ const state = useKeepAlive(rawAttrs);
90
132
  // Modal.js: onDismiss is iOS-only — it drops the keep-alive, then tells the app.
91
133
  const handleDismiss = () => {
92
134
  if (Platform.OS !== 'ios')
@@ -101,26 +143,13 @@ export const Modal = defineComponent((_props, { attrs: rawAttrs, slots, emit })
101
143
  return null;
102
144
  }
103
145
  const attrs = normalizeVueAttrs(rawAttrs);
104
- const root = renderModal({
105
- visible: asBoolean(attrs.visible),
106
- transparent: asBoolean(attrs.transparent),
107
- backdropColor: asString(attrs.backdropColor),
108
- animationType: asAnimationType(attrs.animationType),
109
- presentationStyle: asPresentationStyle(attrs.presentationStyle),
110
- supportedOrientations: asSupportedOrientations(attrs.supportedOrientations),
111
- hardwareAccelerated: asBoolean(attrs.hardwareAccelerated),
112
- statusBarTranslucent: asBoolean(attrs.statusBarTranslucent),
113
- navigationBarTranslucent: asBoolean(attrs.navigationBarTranslucent),
114
- allowSwipeDismissal: asBoolean(attrs.allowSwipeDismissal),
115
- style: isStyleProp(attrs.style) ? attrs.style : undefined,
116
- passthrough: resolveAccessibilityProps(forwardAttrs(attrs)),
117
- });
146
+ const root = modalRootOf(attrs);
118
147
  // root = modal > [container]; the slot children nest UNDER the container View, never
119
148
  // as a direct sibling of the host (RN's modal content layout).
120
149
  const [container] = root.children;
121
150
  if (typeof container === 'string')
122
151
  return null;
123
- const slotChildren = slots.default !== undefined ? slots.default() : [];
152
+ const slotChildren = slots.default?.() ?? [];
124
153
  return h(root.type, {
125
154
  ...root.props,
126
155
  key: root.key,
@@ -1,16 +1,16 @@
1
- import type { IClassNameValue } from '@symbiote-native/engine';
1
+ import type { IClassNameValue, IColorValue } from '@symbiote-native/engine';
2
2
  import type { IAccessibilityProps, IAriaProps } from '@symbiote-native/components';
3
- export interface IRefreshControlProps extends IAccessibilityProps, IAriaProps {
3
+ export type IRefreshControlProps = IAccessibilityProps & IAriaProps & {
4
4
  refreshing: boolean;
5
5
  id?: string;
6
6
  onRefresh?: () => void | Promise<void>;
7
- tintColor?: string;
7
+ tintColor?: IColorValue;
8
8
  title?: string;
9
- titleColor?: string;
9
+ titleColor?: IColorValue;
10
10
  progressViewOffset?: number;
11
- colors?: readonly string[];
12
- progressBackgroundColor?: string;
11
+ colors?: readonly IColorValue[];
12
+ progressBackgroundColor?: IColorValue;
13
13
  size?: 'default' | 'large';
14
14
  enabled?: boolean;
15
15
  class?: IClassNameValue;
16
- }
16
+ };
@@ -1,8 +1,8 @@
1
- import type { IClassNameValue, ISymbioteEvent, IStyleProp, IViewStyle } from '@symbiote-native/engine';
2
- import type { IAccessibilityProps, IAriaProps } from '@symbiote-native/components';
1
+ import type { IClassNameValue, IColorValue, ISymbioteEvent, IStyleProp, IViewStyle } from '@symbiote-native/engine';
2
+ import type { IAccessibilityProps, IAriaProps, IInnerViewRef, IScrollViewNativeOnlyProps } from '@symbiote-native/components';
3
3
  export type { IScrollViewHandle } from '@symbiote-native/components';
4
4
  type IScrollHandler = (event: ISymbioteEvent) => void;
5
- export interface IScrollViewProps extends IAccessibilityProps, IAriaProps {
5
+ export type IScrollViewProps = IAccessibilityProps & IAriaProps & IScrollViewNativeOnlyProps & {
6
6
  style?: IStyleProp<IViewStyle>;
7
7
  contentContainerStyle?: IStyleProp<IViewStyle> | string;
8
8
  horizontal?: boolean;
@@ -31,8 +31,11 @@ export interface IScrollViewProps extends IAccessibilityProps, IAriaProps {
31
31
  snapToStart?: boolean;
32
32
  snapToEnd?: boolean;
33
33
  disableIntervalMomentum?: boolean;
34
+ experimental_endDraggingSensitivityMultiplier?: number;
34
35
  stickyHeaderIndices?: number[];
35
36
  invertStickyHeaders?: boolean;
37
+ stickyHeaderHiddenOnScroll?: boolean;
38
+ innerViewRef?: IInnerViewRef;
36
39
  keyboardDismissMode?: 'none' | 'on-drag' | 'interactive';
37
40
  keyboardShouldPersistTaps?: boolean | 'always' | 'never' | 'handled';
38
41
  maintainVisibleContentPosition?: {
@@ -61,7 +64,7 @@ export interface IScrollViewProps extends IAccessibilityProps, IAriaProps {
61
64
  overScrollMode?: 'auto' | 'always' | 'never';
62
65
  fadingEdgeLength?: number;
63
66
  persistentScrollbar?: boolean;
64
- endFillColor?: string;
67
+ endFillColor?: IColorValue;
65
68
  onLayout?: IScrollHandler;
66
69
  onScroll?: IScrollHandler;
67
70
  onScrollBeginDrag?: IScrollHandler;
@@ -70,4 +73,4 @@ export interface IScrollViewProps extends IAccessibilityProps, IAriaProps {
70
73
  onMomentumScrollEnd?: IScrollHandler;
71
74
  onScrollToTop?: IScrollHandler;
72
75
  class?: IClassNameValue;
73
- }
76
+ };
@@ -1,4 +1,4 @@
1
- import type { IVirtualizedSectionListHandle } from '@symbiote-native/components';
1
+ import { type IVirtualizedSectionListHandle } from '@symbiote-native/components';
2
2
  import { type IVirtualizedSectionListEmits, type IVirtualizedSectionListProps, type IVirtualizedSectionListSlots } from '../virtualized-section-list';
3
3
  export type { ISection } from '../virtualized-section-list';
4
4
  export type ISectionListHandle = IVirtualizedSectionListHandle;
@@ -9,6 +9,7 @@
9
9
  // straight onto VirtualizedSectionList; only the three synthesized events are bridged, gated on
10
10
  // listener presence so the inner list keeps its RefreshControl/edge-reached gating.
11
11
  import { defineComponent, getCurrentInstance, h, shallowRef, } from '@vue/runtime-core';
12
+ import { routeScrollHandle, } from '@symbiote-native/components';
12
13
  import { VirtualizedSectionList, } from '../virtualized-section-list/index.js';
13
14
  import { normalizeVueAttrs } from '../../utils/normalize-attrs.js';
14
15
  // VirtualizedSectionList's generic construct signature can't be resolved by h()'s overloads, so
@@ -24,12 +25,7 @@ const EMIT_KEYS = ['endReached', 'startReached', 'refresh'];
24
25
  function buildDelegate(getInner) {
25
26
  return {
26
27
  scrollToLocation: params => getInner()?.scrollToLocation(params),
27
- flashScrollIndicators: () => getInner()?.flashScrollIndicators(),
28
- getNativeScrollRef: () => getInner()?.getNativeScrollRef() ?? null,
29
- getScrollableNode: () => getInner()?.getScrollableNode() ?? null,
30
- getScrollResponder: () => getInner()?.getScrollResponder() ?? null,
31
- getScrollNode: () => getInner()?.getScrollNode() ?? null,
32
- recordInteraction: () => getInner()?.recordInteraction(),
28
+ ...routeScrollHandle(getInner),
33
29
  };
34
30
  }
35
31
  export const SectionList = defineComponent(
@@ -1,7 +1,7 @@
1
1
  import type { IClassNameValue, IStyleProp, ISymbioteEvent, ITextStyle } from '@symbiote-native/engine';
2
- import type { IAccessibilityProps, IAriaProps } from '@symbiote-native/components';
2
+ import type { IAccessibilityProps, IAriaProps, ITextNativeOnlyProps } from '@symbiote-native/components';
3
3
  import type { VNodeRef } from '@vue/runtime-core';
4
- export interface ITextProps extends IAccessibilityProps, IAriaProps {
4
+ export type ITextProps = IAccessibilityProps & IAriaProps & ITextNativeOnlyProps & {
5
5
  style?: IStyleProp<ITextStyle>;
6
6
  class?: IClassNameValue;
7
7
  onPress?: (event: ISymbioteEvent) => void;
@@ -21,4 +21,4 @@ export interface ITextProps extends IAccessibilityProps, IAriaProps {
21
21
  selectionColor?: string;
22
22
  ref?: VNodeRef;
23
23
  key?: string | number | symbol;
24
- }
24
+ };
@@ -1,17 +1,17 @@
1
- import type { IStyleProp, IViewStyle } from '@symbiote-native/engine';
1
+ import type { IColorValue, IStyleProp, IViewStyle } from '@symbiote-native/engine';
2
2
  import type { IPressTimingProps } from '@symbiote-native/components';
3
3
  import type { IPressableProps } from './pressable-props';
4
4
  export type ITouchableBaseProps = Omit<IPressableProps, 'style'> & IPressTimingProps & {
5
5
  style?: IStyleProp<IViewStyle>;
6
6
  };
7
- export interface ITouchableOpacityProps extends ITouchableBaseProps {
7
+ export type ITouchableOpacityProps = ITouchableBaseProps & {
8
8
  activeOpacity?: number;
9
- }
10
- export interface ITouchableHighlightProps extends ITouchableBaseProps {
9
+ };
10
+ export type ITouchableHighlightProps = ITouchableBaseProps & {
11
11
  activeOpacity?: number;
12
- underlayColor?: string;
12
+ underlayColor?: IColorValue;
13
13
  touchSoundDisabled?: boolean;
14
14
  testOnly_pressed?: boolean;
15
15
  onShowUnderlay?: () => void;
16
16
  onHideUnderlay?: () => void;
17
- }
17
+ };