@symbiote-native/slider 5.0.0 → 5.0.1

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 (45) hide show
  1. package/package.json +10 -9
  2. package/build/angular/index.d.ts +0 -3
  3. package/build/angular/index.js +0 -2
  4. package/build/angular/slider/index.android.d.ts +0 -9
  5. package/build/angular/slider/index.android.js +0 -71
  6. package/build/angular/slider/index.d.ts +0 -2
  7. package/build/angular/slider/index.ios.d.ts +0 -11
  8. package/build/angular/slider/index.ios.js +0 -72
  9. package/build/angular/slider/index.js +0 -3
  10. package/build/angular/slider/shared.d.ts +0 -84
  11. package/build/angular/slider/shared.js +0 -649
  12. package/build/core/constants.d.ts +0 -20
  13. package/build/core/constants.js +0 -40
  14. package/build/core/index.d.ts +0 -7
  15. package/build/core/index.js +0 -10
  16. package/build/core/render-slider.d.ts +0 -11
  17. package/build/core/render-slider.js +0 -66
  18. package/build/core/render-steps-indicator.d.ts +0 -28
  19. package/build/core/render-steps-indicator.js +0 -91
  20. package/build/core/slider-props.d.ts +0 -52
  21. package/build/core/slider-props.js +0 -7
  22. package/build/core/slider-state.d.ts +0 -15
  23. package/build/core/slider-state.js +0 -73
  24. package/build/react/index.d.ts +0 -4
  25. package/build/react/index.js +0 -7
  26. package/build/react/slider/index.android.d.ts +0 -1
  27. package/build/react/slider/index.android.js +0 -10
  28. package/build/react/slider/index.d.ts +0 -1
  29. package/build/react/slider/index.ios.d.ts +0 -1
  30. package/build/react/slider/index.ios.js +0 -11
  31. package/build/react/slider/index.js +0 -3
  32. package/build/react/slider/shared.d.ts +0 -10
  33. package/build/react/slider/shared.js +0 -176
  34. package/build/register/index.d.ts +0 -1
  35. package/build/register/index.js +0 -47
  36. package/build/vue/index.d.ts +0 -3
  37. package/build/vue/index.js +0 -7
  38. package/build/vue/slider/index.android.d.ts +0 -18
  39. package/build/vue/slider/index.android.js +0 -10
  40. package/build/vue/slider/index.d.ts +0 -2
  41. package/build/vue/slider/index.ios.d.ts +0 -18
  42. package/build/vue/slider/index.ios.js +0 -11
  43. package/build/vue/slider/index.js +0 -3
  44. package/build/vue/slider/shared.d.ts +0 -26
  45. package/build/vue/slider/shared.js +0 -253
@@ -1,176 +0,0 @@
1
- // Slider, the React lifecycle half. The logic (value/limit/disabled folds, the step-option layout)
2
- // and the native render live in @symbiote-native/slider core, shared verbatim with the Vue adapter; here
3
- // React supplies the lifecycle — useState for the last-reported value (to mark the active step)
4
- // and the measured width (for the step indicator), useEffect for the limit warning — plus the
5
- // descriptor bridge and forwardRef to the native view (matching the library's forwardRef). The
6
- // native RNCSlider carries no symbiote metadata: the engine derives its events and color/image
7
- // processors from the library's ViewConfig, registered by ../register (the package barrel pulls
8
- // it). This is the React twin of the Vue wrapper; both drive the engine, NEITHER imports the
9
- // library's React Slider component — that component internally calls React hooks off the React
10
- // dispatcher, so a non-React adapter rendering it directly would crash on a null dispatcher.
11
- import { createElement, forwardRef, useCallback, useEffect, useState } from 'react';
12
- import { descriptorToReact, Image } from '@symbiote-native/react';
13
- import { resolveAccessibilityProps } from '@symbiote-native/components';
14
- import { dlog } from '@symbiote-native/engine';
15
- import { sanitizeSliderValue, resolveSliderDisabled, resolveSliderAccessibilityState, resolveSliderLowerLimit, resolveSliderUpperLimit, valueFromSliderEvent, shouldRenderStepsIndicator, resolveThumbTintColor, shouldPassNativeThumbImage, isInvalidLimitConfig, computeStepOptions, orderStepOptions, stepNumberFontSize, renderSlider, renderSliderNative, resolveStepsContainerStyle, renderStepsIndicator, STEP_INDICATOR_ELEMENT_STYLE, TRACK_MARK_CONTAINER_STYLE, THUMB_IMAGE_CONTAINER_STYLE, THUMB_IMAGE_STYLE, STEP_NUMBER_CONTAINER_STYLE, SLIDER_DEFAULT_MINIMUM_VALUE, SLIDER_DEFAULT_MAXIMUM_VALUE, SLIDER_DEFAULT_STEP, SLIDER_ON_CHANGE, SLIDER_ON_VALUE_CHANGE, SLIDER_ON_SLIDING_START, SLIDER_ON_SLIDING_COMPLETE, SLIDER_ON_ACCESSIBILITY_ACTION, } from '../../core/index.js';
16
- function isRecord(value) {
17
- return typeof value === 'object' && value !== null && !Array.isArray(value);
18
- }
19
- function toChild(child) {
20
- return typeof child === 'string' ? child : descriptorToReact(child);
21
- }
22
- // renderSlider's Descriptor IS the outer wrapper (symbiote-view hosting the native leaf +
23
- // optional steps overlay); className is reapplied on it here, exactly like ImageBackground's
24
- // wrapper.
25
- function withClassName(descriptor, className) {
26
- return createElement(descriptor.type, { key: descriptor.key, ...descriptor.props, className }, ...descriptor.children.map(toChild));
27
- }
28
- export function createSlider(platform) {
29
- return forwardRef((rawProps, forwardedRef) => {
30
- // Slider owns its host wrapper rather than rendering through a symbiote View, so it folds
31
- // aria/role into accessibility* here; the rest (testID, accessibilityLabel, accessibility-
32
- // Actions, …) rides to the native node via `passthrough`.
33
- const props = resolveAccessibilityProps(rawProps);
34
- const { value, minimumValue, maximumValue, step, lowerLimit, upperLimit, disabled, inverted, thumbTintColor, thumbImage, accessibilityState, renderStepNumber, style, className, StepMarker, onValueChange, onSlidingStart, onSlidingComplete, onAccessibilityAction, ...passthrough } = props;
35
- const [reportedValue, setReportedValue] = useState(undefined);
36
- const [width, setWidth] = useState(0);
37
- const handleValueChange = useCallback((event) => {
38
- const next = valueFromSliderEvent(event);
39
- if (next === undefined)
40
- return;
41
- setReportedValue(next);
42
- onValueChange?.(next);
43
- }, [onValueChange]);
44
- const handleSlidingStart = useCallback((event) => {
45
- const next = valueFromSliderEvent(event);
46
- if (next !== undefined)
47
- onSlidingStart?.(next);
48
- }, [onSlidingStart]);
49
- const handleSlidingComplete = useCallback((event) => {
50
- const next = valueFromSliderEvent(event);
51
- if (next !== undefined)
52
- onSlidingComplete?.(next);
53
- }, [onSlidingComplete]);
54
- const handleAccessibilityAction = useCallback((event) => {
55
- onAccessibilityAction?.(event);
56
- }, [onAccessibilityAction]);
57
- const handleLayout = useCallback((event) => {
58
- const layout = event.nativeEvent.layout;
59
- if (isRecord(layout) && typeof layout.width === 'number')
60
- setWidth(layout.width);
61
- }, []);
62
- const minimum = minimumValue ?? SLIDER_DEFAULT_MINIMUM_VALUE;
63
- const maximum = maximumValue ?? SLIDER_DEFAULT_MAXIMUM_VALUE;
64
- const stepValue = step ?? SLIDER_DEFAULT_STEP;
65
- const lower = resolveSliderLowerLimit(lowerLimit);
66
- const upper = resolveSliderUpperLimit(upperLimit);
67
- useEffect(() => {
68
- if (isInvalidLimitConfig(lower, upper))
69
- dlog('Slider: lowerLimit must be smaller than upperLimit');
70
- }, [lower, upper]);
71
- const hasStepMarker = StepMarker !== undefined;
72
- const hasThumbImage = thumbImage !== undefined;
73
- const showSteps = shouldRenderStepsIndicator(hasStepMarker, renderStepNumber);
74
- // Passed to the native view raw — the engine runs the image processor derived from RNCSlider's
75
- // ViewConfig — and only when no custom marker draws its own thumb (matches the library).
76
- const nativeThumbImage = shouldPassNativeThumbImage(hasStepMarker, hasThumbImage)
77
- ? thumbImage
78
- : undefined;
79
- const view = {
80
- value: sanitizeSliderValue(value),
81
- minimumValue: minimum,
82
- maximumValue: maximum,
83
- step: stepValue,
84
- lowerLimit: lower,
85
- upperLimit: upper,
86
- disabled: resolveSliderDisabled(disabled, accessibilityState),
87
- inverted: inverted ?? false,
88
- thumbTintColor: resolveThumbTintColor(thumbTintColor, hasStepMarker, hasThumbImage),
89
- thumbImage: nativeThumbImage,
90
- accessibilityState: resolveSliderAccessibilityState(disabled, accessibilityState),
91
- width,
92
- style,
93
- passthrough: {
94
- ...passthrough,
95
- ref: forwardedRef,
96
- [SLIDER_ON_CHANGE]: handleValueChange,
97
- [SLIDER_ON_VALUE_CHANGE]: handleValueChange,
98
- [SLIDER_ON_SLIDING_START]: handleSlidingStart,
99
- [SLIDER_ON_SLIDING_COMPLETE]: handleSlidingComplete,
100
- [SLIDER_ON_ACCESSIBILITY_ACTION]: handleAccessibilityAction,
101
- },
102
- };
103
- if (!showSteps) {
104
- return withClassName(renderSlider(view, platform, { onLayout: handleLayout }), className);
105
- }
106
- const options = computeStepOptions(minimum, maximum, stepValue, platform.stepResolution);
107
- const currentValue = reportedValue ?? view.value ?? minimum;
108
- if (StepMarker !== undefined) {
109
- const overlay = renderCustomStepsOverlay({
110
- options,
111
- currentValue,
112
- inverted: view.inverted,
113
- renderStepNumber: renderStepNumber === true,
114
- thumbImage,
115
- width,
116
- platform,
117
- StepMarker,
118
- });
119
- return createElement('symbiote-view', { style: resolveStepsWrapperStyle(style, platform), onLayout: handleLayout, className }, overlay, descriptorToReact(renderSliderNative(view, platform)));
120
- }
121
- const steps = renderStepsIndicator({
122
- options,
123
- currentValue,
124
- width,
125
- renderStepNumber: renderStepNumber === true,
126
- thumbImage,
127
- inverted: view.inverted,
128
- platform,
129
- });
130
- return withClassName(renderSlider(view, platform, { steps, onLayout: handleLayout }), className);
131
- });
132
- }
133
- // The wrapper style for the custom-marker steps path mirrors renderSlider's wrapper.
134
- function resolveStepsWrapperStyle(style, platform) {
135
- return [platform.defaultStyle, style, { justifyContent: 'center' }];
136
- }
137
- // The custom-marker overlay: the same flex row as the agnostic default, each mark hosting the
138
- // user's StepMarker element (plus the thumbImage on the current step and the optional step
139
- // number), mirroring the library's SliderTrackMark composition.
140
- function renderCustomStepsOverlay(params) {
141
- const fontSize = stepNumberFontSize(params.options.length);
142
- const ordered = orderStepOptions(params.options, params.inverted);
143
- const min = params.options[0];
144
- const max = params.options[params.options.length - 1];
145
- const cells = ordered.map((value, index) => {
146
- const trackChildren = [
147
- createElement(params.StepMarker, {
148
- key: 'marker',
149
- stepMarked: value === params.currentValue,
150
- currentValue: params.currentValue,
151
- index,
152
- min,
153
- max,
154
- }),
155
- ];
156
- if (params.thumbImage !== undefined && value === params.currentValue) {
157
- trackChildren.push(createElement('symbiote-view', {
158
- key: 'thumb',
159
- style: THUMB_IMAGE_CONTAINER_STYLE,
160
- testID: 'sliderTrackMark-thumbImage',
161
- }, createElement(Image, { source: params.thumbImage, style: THUMB_IMAGE_STYLE })));
162
- }
163
- const cellChildren = [
164
- createElement('symbiote-view', { key: 'track', style: TRACK_MARK_CONTAINER_STYLE }, ...trackChildren),
165
- ];
166
- if (params.renderStepNumber) {
167
- cellChildren.push(createElement('symbiote-view', { key: 'number', style: STEP_NUMBER_CONTAINER_STYLE }, createElement('symbiote-text', { testID: `${index}th-step`, style: { fontSize } }, String(value))));
168
- }
169
- return createElement('symbiote-view', { key: index, style: STEP_INDICATOR_ELEMENT_STYLE }, ...cellChildren);
170
- });
171
- return createElement('symbiote-view', {
172
- pointerEvents: 'none',
173
- testID: 'StepsIndicator-Container',
174
- style: resolveStepsContainerStyle(params.width, params.platform),
175
- }, ...cells);
176
- }
@@ -1 +0,0 @@
1
- import '@react-native-community/slider/dist/RNCSliderNativeComponent';
@@ -1,47 +0,0 @@
1
- // Side-effect: register RNCSlider's ViewConfig in RN's ReactNativeViewConfigRegistry. Evaluating
2
- // the codegen native-component spec (codegenNativeComponent('RNCSlider', …)) registers the
3
- // ViewConfig getter the engine reads via setNativeViewConfigSource, so the engine can derive
4
- // RNCSlider's events and color/image processors on first commit. We pull the codegen SPEC, never
5
- // the library's default export (Slider.tsx, a React component using hooks) — this module has no
6
- // hooks, so it is framework-agnostic and shared by every adapter entry (vue / react). Each adapter
7
- // barrel imports it; the component modules and their headless tests stay free of the spec.
8
- import '@react-native-community/slider/dist/RNCSliderNativeComponent';
9
- import { processColor, registerComponent } from '@symbiote-native/engine';
10
- import { RNC_SLIDER_VIEW_NAME, SLIDER_ON_CHANGE, SLIDER_ON_VALUE_CHANGE, SLIDER_ON_SLIDING_START, SLIDER_ON_SLIDING_COMPLETE, SLIDER_ON_ACCESSIBILITY_ACTION, } from '../core/index.js';
11
- function listenerName(prop) {
12
- return prop.charAt(2).toLowerCase() + prop.slice(3);
13
- }
14
- function processSliderColor(value) {
15
- return processColor(value);
16
- }
17
- // Fallback metadata for the native slider. RN's codegen module above normally registers the same
18
- // ViewConfig into ReactNativeViewConfigRegistry, but relying only on that side effect is brittle on
19
- // Metro/RN version boundaries: if the registry lookup misses, the slider still mounts, but tint
20
- // props stay raw and native value events never route back to JS. Keep the explicit fallback here,
21
- // in the wrapper package that owns RNCSlider, so React/Vue/Angular all inherit the same behavior.
22
- registerComponent(RNC_SLIDER_VIEW_NAME, {
23
- events: [
24
- { raw: 'topChange', listener: listenerName(SLIDER_ON_CHANGE) },
25
- { raw: 'topRNCSliderValueChange', listener: listenerName(SLIDER_ON_VALUE_CHANGE) },
26
- {
27
- raw: 'topRNCSliderSlidingStart',
28
- listener: listenerName(SLIDER_ON_SLIDING_START),
29
- direct: true,
30
- },
31
- {
32
- raw: 'topRNCSliderSlidingComplete',
33
- listener: listenerName(SLIDER_ON_SLIDING_COMPLETE),
34
- direct: true,
35
- },
36
- {
37
- raw: 'topRNCSliderAccessibilityAction',
38
- listener: listenerName(SLIDER_ON_ACCESSIBILITY_ACTION),
39
- direct: true,
40
- },
41
- ],
42
- processors: {
43
- minimumTrackTintColor: processSliderColor,
44
- maximumTrackTintColor: processSliderColor,
45
- thumbTintColor: processSliderColor,
46
- },
47
- });
@@ -1,3 +0,0 @@
1
- import '../register';
2
- export { Slider } from './slider';
3
- export type { ISliderProps } from './slider/shared';
@@ -1,7 +0,0 @@
1
- // @symbiote-native/slider/vue: the Vue wrapper over the @react-native-community/slider native view.
2
- // Importing this barrel first registers the native view's ViewConfig (./register, a side-effect
3
- // import of the codegen spec — never the library's React component), then exposes the platform-
4
- // split Slider. The public prop type is the agnostic core ISliderProps plus a Vue-only `class`
5
- // field (see slider/shared.ts); the custom StepMarker is a Vue scoped slot, not a prop.
6
- import '../register';
7
- export { Slider } from './slider';
@@ -1,18 +0,0 @@
1
- export declare const Slider: import("vue").DefineSetupFnComponent<import("./shared").ISliderProps, {
2
- valueChange: (value: number) => boolean;
3
- slidingStart: (value: number) => boolean;
4
- slidingComplete: (value: number) => boolean;
5
- accessibilityAction: (event: import("@symbiote-native/engine").ISymbioteEvent) => boolean;
6
- 'update:modelValue': (value: number) => boolean;
7
- 'update:value': (value: number) => boolean;
8
- }, {}, Omit<import("../../core").ISliderProps, "onValueChange" | "onSlidingStart" | "onSlidingComplete" | "onAccessibilityAction"> & {
9
- modelValue?: number;
10
- class?: import("@symbiote-native/engine").IClassNameValue;
11
- } & {
12
- onValueChange?: ((value: number) => any) | undefined;
13
- onSlidingStart?: ((value: number) => any) | undefined;
14
- onSlidingComplete?: ((value: number) => any) | undefined;
15
- onAccessibilityAction?: ((event: import("@symbiote-native/engine").ISymbioteEvent) => any) | undefined;
16
- "onUpdate:modelValue"?: ((value: number) => any) | undefined;
17
- "onUpdate:value"?: ((value: number) => any) | undefined;
18
- }, import("vue").PublicProps>;
@@ -1,10 +0,0 @@
1
- // Slider on Android: the native view sizes itself (no default height), lays out implicit steps at
2
- // 128-point resolution, and keeps the step row at the top. Mirrors the library's Android branches.
3
- import { createSlider } from './shared.js';
4
- import { SLIDER_STEP_RESOLUTION_ANDROID } from '../../core/index.js';
5
- const ANDROID_STEPS_CONTAINER_TOP = 0;
6
- export const Slider = createSlider({
7
- defaultStyle: {},
8
- stepResolution: SLIDER_STEP_RESOLUTION_ANDROID,
9
- stepsContainerTop: ANDROID_STEPS_CONTAINER_TOP,
10
- });
@@ -1,2 +0,0 @@
1
- export { Slider } from './index.ios';
2
- export type { ISliderProps } from './shared';
@@ -1,18 +0,0 @@
1
- export declare const Slider: import("vue").DefineSetupFnComponent<import("./shared").ISliderProps, {
2
- valueChange: (value: number) => boolean;
3
- slidingStart: (value: number) => boolean;
4
- slidingComplete: (value: number) => boolean;
5
- accessibilityAction: (event: import("@symbiote-native/engine").ISymbioteEvent) => boolean;
6
- 'update:modelValue': (value: number) => boolean;
7
- 'update:value': (value: number) => boolean;
8
- }, {}, Omit<import("../../core").ISliderProps, "onValueChange" | "onSlidingStart" | "onSlidingComplete" | "onAccessibilityAction"> & {
9
- modelValue?: number;
10
- class?: import("@symbiote-native/engine").IClassNameValue;
11
- } & {
12
- onValueChange?: ((value: number) => any) | undefined;
13
- onSlidingStart?: ((value: number) => any) | undefined;
14
- onSlidingComplete?: ((value: number) => any) | undefined;
15
- onAccessibilityAction?: ((event: import("@symbiote-native/engine").ISymbioteEvent) => any) | undefined;
16
- "onUpdate:modelValue"?: ((value: number) => any) | undefined;
17
- "onUpdate:value"?: ((value: number) => any) | undefined;
18
- }, import("vue").PublicProps>;
@@ -1,11 +0,0 @@
1
- // Slider on iOS: the native view has an intrinsic 40pt height (the wrapper applies it as the
2
- // default), lays out implicit steps at 1000-point resolution, and nudges the step row down 10pt.
3
- // Also the base (./index re-exports it) for headless. Mirrors the library's iOS branches.
4
- import { createSlider } from './shared.js';
5
- import { SLIDER_IOS_DEFAULT_HEIGHT, SLIDER_STEP_RESOLUTION_IOS } from '../../core/index.js';
6
- const IOS_STEPS_CONTAINER_TOP = 10;
7
- export const Slider = createSlider({
8
- defaultStyle: { height: SLIDER_IOS_DEFAULT_HEIGHT },
9
- stepResolution: SLIDER_STEP_RESOLUTION_IOS,
10
- stepsContainerTop: IOS_STEPS_CONTAINER_TOP,
11
- });
@@ -1,3 +0,0 @@
1
- // Base entry for headless / tsc: re-exports the iOS variant (Metro picks index.ios / index.android
2
- // per platform via filename selection; this no-suffix file is what tsx/vitest resolve).
3
- export { Slider } from './index.ios.js';
@@ -1,26 +0,0 @@
1
- import { type IClassNameValue, type ISymbioteEvent } from '@symbiote-native/engine';
2
- import { type ISliderPlatform, type ISliderProps as ISliderBaseProps } from '../../core';
3
- export type ISliderProps = Omit<ISliderBaseProps, 'onValueChange' | 'onSlidingStart' | 'onSlidingComplete' | 'onAccessibilityAction'> & {
4
- modelValue?: number;
5
- class?: IClassNameValue;
6
- };
7
- type ISliderEmits = {
8
- valueChange: (value: number) => boolean;
9
- slidingStart: (value: number) => boolean;
10
- slidingComplete: (value: number) => boolean;
11
- accessibilityAction: (event: ISymbioteEvent) => boolean;
12
- 'update:modelValue': (value: number) => boolean;
13
- 'update:value': (value: number) => boolean;
14
- };
15
- export declare function createSlider(platform: ISliderPlatform): import("vue").DefineSetupFnComponent<ISliderProps, ISliderEmits, {}, Omit<ISliderBaseProps, "onValueChange" | "onSlidingStart" | "onSlidingComplete" | "onAccessibilityAction"> & {
16
- modelValue?: number;
17
- class?: IClassNameValue;
18
- } & {
19
- onValueChange?: ((value: number) => any) | undefined;
20
- onSlidingStart?: ((value: number) => any) | undefined;
21
- onSlidingComplete?: ((value: number) => any) | undefined;
22
- onAccessibilityAction?: ((event: ISymbioteEvent) => any) | undefined;
23
- "onUpdate:modelValue"?: ((value: number) => any) | undefined;
24
- "onUpdate:value"?: ((value: number) => any) | undefined;
25
- }, import("vue").PublicProps>;
26
- export {};
@@ -1,253 +0,0 @@
1
- // Slider, the Vue lifecycle half. The logic (value/limit/disabled folds, the step-option layout)
2
- // and the native render live in @symbiote-native/slider core, shared verbatim with the future Angular
3
- // adapter; here Vue supplies the reactivity — a ref tracks the value native last reported (to mark
4
- // the active step) and a ref the measured width (for the step indicator) — plus the descriptor
5
- // bridge. The native RNCSlider view carries no symbiote metadata: the engine derives its events
6
- // and color processors from the library's ViewConfig at runtime. That ViewConfig is registered by
7
- // the side-effect import in ../register (pulled in by the package barrel, NOT here, so this module
8
- // and its tests stay free of the third-party spec).
9
- import { defineComponent, h, ref } from '@vue/runtime-core';
10
- import { descriptorToVue, normalizeVueAttrs, resolveModelValue, emitModelUpdate, Image, } from '@symbiote-native/vue';
11
- import { dlog } from '@symbiote-native/engine';
12
- import { sanitizeSliderValue, resolveSliderDisabled, resolveSliderAccessibilityState, resolveSliderLowerLimit, resolveSliderUpperLimit, valueFromSliderEvent, shouldRenderStepsIndicator, resolveThumbTintColor, shouldPassNativeThumbImage, isInvalidLimitConfig, computeStepOptions, orderStepOptions, stepNumberFontSize, renderSlider, renderSliderNative, resolveStepsContainerStyle, renderStepsIndicator, STEP_INDICATOR_ELEMENT_STYLE, TRACK_MARK_CONTAINER_STYLE, THUMB_IMAGE_CONTAINER_STYLE, THUMB_IMAGE_STYLE, STEP_NUMBER_CONTAINER_STYLE, SLIDER_DEFAULT_MINIMUM_VALUE, SLIDER_DEFAULT_MAXIMUM_VALUE, SLIDER_DEFAULT_STEP, SLIDER_ON_CHANGE, SLIDER_ON_VALUE_CHANGE, SLIDER_ON_SLIDING_START, SLIDER_ON_SLIDING_COMPLETE, SLIDER_ON_ACCESSIBILITY_ACTION, } from '../../core/index.js';
13
- function isRecord(value) {
14
- return typeof value === 'object' && value !== null && !Array.isArray(value);
15
- }
16
- function asNumber(value) {
17
- return typeof value === 'number' ? value : undefined;
18
- }
19
- function isNumber(value) {
20
- return typeof value === 'number';
21
- }
22
- function asBoolean(value) {
23
- return typeof value === 'boolean' ? value : undefined;
24
- }
25
- function isImageSourceProp(value) {
26
- return typeof value === 'number' || (typeof value === 'object' && value !== null);
27
- }
28
- // accessibilityState arrives untyped; narrow its disabled flag for the fold while preserving the
29
- // other entries at runtime (the resolve spread carries them onto the native node).
30
- function asAccessibilityState(value) {
31
- if (!isRecord(value))
32
- return undefined;
33
- return { ...value, disabled: typeof value.disabled === 'boolean' ? value.disabled : undefined };
34
- }
35
- // The props/handlers the lifecycle consumes itself; everything else (the track tints + images,
36
- // thumbSize, tapToSeek, vertical, accessibility*/testID/aria-*) forwards onto the native node.
37
- // The four callbacks are JS-only and must NEVER reach Fabric, so they are stripped here and
38
- // re-supplied as the native event handlers in `passthrough`. `class` is handled too: like
39
- // `style`, it targets the WRAPPER `symbiote-view` (renderSlider's outer element), never the
40
- // inner native slider leaf `passthrough` reaches — applied explicitly by each render path below.
41
- const HANDLED_ATTRS = [
42
- 'value',
43
- 'modelValue',
44
- 'minimumValue',
45
- 'maximumValue',
46
- 'step',
47
- 'lowerLimit',
48
- 'upperLimit',
49
- 'disabled',
50
- 'inverted',
51
- 'thumbTintColor',
52
- 'thumbImage',
53
- 'accessibilityState',
54
- 'renderStepNumber',
55
- 'style',
56
- 'class',
57
- 'onValueChange',
58
- 'onSlidingStart',
59
- 'onSlidingComplete',
60
- 'onAccessibilityAction',
61
- ];
62
- function forwardAttrs(attrs) {
63
- const result = {};
64
- for (const key of Object.keys(attrs)) {
65
- if (!HANDLED_ATTRS.includes(key))
66
- result[key] = attrs[key];
67
- }
68
- return result;
69
- }
70
- // renderSlider builds the wrapper `symbiote-view` descriptor itself, with no `class` field of
71
- // its own (packages/slider/src/core stays framework-agnostic). `class` is stamped onto that
72
- // returned descriptor's props here, right before the Vue bridge, so it lands on the SAME
73
- // element `style` targets rather than the inner native leaf `passthrough` would otherwise
74
- // carry it to (the ImageBackground-style wrapper/inner split).
75
- function applyWrapperClass(descriptor, classValue) {
76
- descriptor.props = { ...descriptor.props, class: classValue };
77
- return descriptor;
78
- }
79
- export function createSlider(platform) {
80
- return defineComponent((_props, { attrs: rawAttrs, slots, emit }) => {
81
- // The value native last reported, kept only to mark the active step in the indicator. Not
82
- // the controlled value — the slider is uncontrolled during a drag (no snap-back).
83
- const reportedValue = ref(undefined);
84
- // The measured wrapper width the step indicator lays out against; 0 until the first layout.
85
- const width = ref(0);
86
- const handleValueChange = (event) => {
87
- const value = valueFromSliderEvent(event);
88
- if (value === undefined)
89
- return;
90
- reportedValue.value = value;
91
- emit('valueChange', value);
92
- emitModelUpdate(emit, value);
93
- };
94
- const handleSlidingStart = (event) => {
95
- const value = valueFromSliderEvent(event);
96
- if (value !== undefined)
97
- emit('slidingStart', value);
98
- };
99
- const handleSlidingComplete = (event) => {
100
- const value = valueFromSliderEvent(event);
101
- if (value !== undefined)
102
- emit('slidingComplete', value);
103
- };
104
- const handleAccessibilityAction = (event) => {
105
- emit('accessibilityAction', event);
106
- };
107
- const handleLayout = (event) => {
108
- const layout = event.nativeEvent.layout;
109
- if (isRecord(layout) && typeof layout.width === 'number')
110
- width.value = layout.width;
111
- };
112
- return () => {
113
- const attrs = normalizeVueAttrs(rawAttrs);
114
- const minimumValue = asNumber(attrs.minimumValue) ?? SLIDER_DEFAULT_MINIMUM_VALUE;
115
- const maximumValue = asNumber(attrs.maximumValue) ?? SLIDER_DEFAULT_MAXIMUM_VALUE;
116
- const step = asNumber(attrs.step) ?? SLIDER_DEFAULT_STEP;
117
- const lowerLimit = resolveSliderLowerLimit(asNumber(attrs.lowerLimit));
118
- const upperLimit = resolveSliderUpperLimit(asNumber(attrs.upperLimit));
119
- if (isInvalidLimitConfig(lowerLimit, upperLimit)) {
120
- dlog('Slider: lowerLimit must be smaller than upperLimit');
121
- }
122
- const disabledAttr = asBoolean(attrs.disabled);
123
- const accessibilityState = asAccessibilityState(attrs.accessibilityState);
124
- const inverted = asBoolean(attrs.inverted) ?? false;
125
- const hasStepMarker = slots.stepMarker !== undefined;
126
- const renderStepNumber = asBoolean(attrs.renderStepNumber);
127
- const hasThumbImage = attrs.thumbImage !== undefined;
128
- const showSteps = shouldRenderStepsIndicator(hasStepMarker, renderStepNumber);
129
- // The native view gets a thumbImage only when there is one AND no custom marker (which
130
- // draws its own thumb), matching the library. We pass the source raw — the engine runs the
131
- // image processor derived from RNCSlider's ViewConfig (same path as the color tints), so
132
- // unlike the plain-RN library wrapper we do NOT pre-call Image.resolveAssetSource here.
133
- const nativeThumbImage = shouldPassNativeThumbImage(hasStepMarker, hasThumbImage)
134
- ? attrs.thumbImage
135
- : undefined;
136
- const view = {
137
- value: sanitizeSliderValue(resolveModelValue(attrs, isNumber)),
138
- minimumValue,
139
- maximumValue,
140
- step,
141
- lowerLimit,
142
- upperLimit,
143
- disabled: resolveSliderDisabled(disabledAttr, accessibilityState),
144
- inverted,
145
- thumbTintColor: resolveThumbTintColor(attrs.thumbTintColor, hasStepMarker, hasThumbImage),
146
- thumbImage: nativeThumbImage,
147
- accessibilityState: resolveSliderAccessibilityState(disabledAttr, accessibilityState),
148
- width: width.value,
149
- style: attrs.style,
150
- passthrough: {
151
- ...forwardAttrs(attrs),
152
- [SLIDER_ON_CHANGE]: handleValueChange,
153
- [SLIDER_ON_VALUE_CHANGE]: handleValueChange,
154
- [SLIDER_ON_SLIDING_START]: handleSlidingStart,
155
- [SLIDER_ON_SLIDING_COMPLETE]: handleSlidingComplete,
156
- [SLIDER_ON_ACCESSIBILITY_ACTION]: handleAccessibilityAction,
157
- },
158
- };
159
- // Common case: no step overlay. The full slider (wrapper + native leaf) renders agnostic.
160
- if (!showSteps) {
161
- return descriptorToVue(applyWrapperClass(renderSlider(view, platform, { onLayout: handleLayout }), attrs.class));
162
- }
163
- const options = computeStepOptions(minimumValue, maximumValue, step, platform.stepResolution);
164
- const currentValue = reportedValue.value ?? view.value ?? minimumValue;
165
- // A custom StepMarker is a per-adapter element (a Vue slot), so its overlay is assembled
166
- // here at the VNode level; the default overlay (numbers / thumbImage only) is the shared
167
- // agnostic render. Either way the wrapper measures width via onLayout.
168
- if (hasStepMarker) {
169
- const overlay = renderCustomStepsOverlay({
170
- options,
171
- currentValue,
172
- minimumValue,
173
- maximumValue,
174
- inverted,
175
- renderStepNumber: renderStepNumber === true,
176
- thumbImage: attrs.thumbImage,
177
- width: width.value,
178
- platform,
179
- stepMarker: slots.stepMarker,
180
- });
181
- return h('symbiote-view', {
182
- style: resolveStepsWrapperStyle(view.style, platform),
183
- class: attrs.class,
184
- onLayout: handleLayout,
185
- }, [overlay, descriptorToVue(renderSliderNative(view, platform))]);
186
- }
187
- const steps = renderStepsIndicator({
188
- options,
189
- currentValue,
190
- width: width.value,
191
- renderStepNumber: renderStepNumber === true,
192
- thumbImage: attrs.thumbImage,
193
- inverted,
194
- platform,
195
- });
196
- return descriptorToVue(applyWrapperClass(renderSlider(view, platform, { steps, onLayout: handleLayout }), attrs.class));
197
- };
198
- }, {
199
- name: 'Slider',
200
- inheritAttrs: false,
201
- emits: {
202
- valueChange: (_value) => true,
203
- slidingStart: (_value) => true,
204
- slidingComplete: (_value) => true,
205
- accessibilityAction: (_event) => true,
206
- 'update:modelValue': (_value) => true,
207
- 'update:value': (_value) => true,
208
- },
209
- });
210
- }
211
- // The wrapper style for the steps path mirrors renderSlider's wrapper (kept here because the
212
- // custom-marker path builds the wrapper itself rather than going through renderSlider).
213
- function resolveStepsWrapperStyle(style, platform) {
214
- return [platform.defaultStyle, style, { justifyContent: 'center' }];
215
- }
216
- // The custom-marker overlay: the same flex row as the agnostic default, but each mark hosts the
217
- // user's `#stepMarker` scoped slot (plus the thumbImage on the current step and the optional step
218
- // number), exactly as the library's SliderTrackMark composes a StepMarker + Image.
219
- function renderCustomStepsOverlay(params) {
220
- const fontSize = stepNumberFontSize(params.options.length);
221
- const ordered = orderStepOptions(params.options, params.inverted);
222
- const min = params.options[0];
223
- const max = params.options[params.options.length - 1];
224
- const cells = ordered.map((value, index) => {
225
- const trackChildren = [];
226
- const marker = params.stepMarker?.({
227
- stepMarked: value === params.currentValue,
228
- currentValue: params.currentValue,
229
- index,
230
- min,
231
- max,
232
- });
233
- if (marker !== undefined)
234
- trackChildren.push(h('symbiote-view', {}, marker));
235
- if (isImageSourceProp(params.thumbImage) && value === params.currentValue) {
236
- trackChildren.push(h('symbiote-view', { style: THUMB_IMAGE_CONTAINER_STYLE, testID: 'sliderTrackMark-thumbImage' }, [h(Image, { source: params.thumbImage, style: THUMB_IMAGE_STYLE })]));
237
- }
238
- const cellChildren = [
239
- h('symbiote-view', { style: TRACK_MARK_CONTAINER_STYLE }, trackChildren),
240
- ];
241
- if (params.renderStepNumber) {
242
- cellChildren.push(h('symbiote-view', { style: STEP_NUMBER_CONTAINER_STYLE }, [
243
- h('symbiote-text', { testID: `${index}th-step`, style: { fontSize } }, String(value)),
244
- ]));
245
- }
246
- return h('symbiote-view', { style: STEP_INDICATOR_ELEMENT_STYLE }, cellChildren);
247
- });
248
- return h('symbiote-view', {
249
- pointerEvents: 'none',
250
- testID: 'StepsIndicator-Container',
251
- style: resolveStepsContainerStyle(params.width, params.platform),
252
- }, cells);
253
- }