@symbiote-native/slider 0.1.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 (109) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +90 -0
  3. package/build/angular/index.d.ts +3 -0
  4. package/build/angular/index.js +2 -0
  5. package/build/angular/slider/index.android.d.ts +9 -0
  6. package/build/angular/slider/index.android.js +71 -0
  7. package/build/angular/slider/index.d.ts +2 -0
  8. package/build/angular/slider/index.ios.d.ts +11 -0
  9. package/build/angular/slider/index.ios.js +72 -0
  10. package/build/angular/slider/index.js +3 -0
  11. package/build/angular/slider/shared.d.ts +84 -0
  12. package/build/angular/slider/shared.js +648 -0
  13. package/build/core/constants.d.ts +20 -0
  14. package/build/core/constants.js +40 -0
  15. package/build/core/index.d.ts +7 -0
  16. package/build/core/index.js +11 -0
  17. package/build/core/render-slider.d.ts +11 -0
  18. package/build/core/render-slider.js +66 -0
  19. package/build/core/render-steps-indicator.d.ts +28 -0
  20. package/build/core/render-steps-indicator.js +91 -0
  21. package/build/core/slider-props.d.ts +52 -0
  22. package/build/core/slider-props.js +6 -0
  23. package/build/core/slider-state.d.ts +15 -0
  24. package/build/core/slider-state.js +73 -0
  25. package/build/react/index.d.ts +4 -0
  26. package/build/react/index.js +7 -0
  27. package/build/react/slider/index.android.d.ts +1 -0
  28. package/build/react/slider/index.android.js +10 -0
  29. package/build/react/slider/index.d.ts +1 -0
  30. package/build/react/slider/index.ios.d.ts +1 -0
  31. package/build/react/slider/index.ios.js +11 -0
  32. package/build/react/slider/index.js +3 -0
  33. package/build/react/slider/shared.d.ts +10 -0
  34. package/build/react/slider/shared.js +175 -0
  35. package/build/register.d.ts +1 -0
  36. package/build/register.js +48 -0
  37. package/build/vue/index.d.ts +3 -0
  38. package/build/vue/index.js +8 -0
  39. package/build/vue/slider/index.android.d.ts +18 -0
  40. package/build/vue/slider/index.android.js +10 -0
  41. package/build/vue/slider/index.d.ts +2 -0
  42. package/build/vue/slider/index.ios.d.ts +18 -0
  43. package/build/vue/slider/index.ios.js +11 -0
  44. package/build/vue/slider/index.js +3 -0
  45. package/build/vue/slider/shared.d.ts +26 -0
  46. package/build/vue/slider/shared.js +254 -0
  47. package/build-ngc/angular/index.d.ts +3 -0
  48. package/build-ngc/angular/index.js +3 -0
  49. package/build-ngc/angular/index.js.map +1 -0
  50. package/build-ngc/angular/slider/index.android.d.ts +12 -0
  51. package/build-ngc/angular/slider/index.android.js +28 -0
  52. package/build-ngc/angular/slider/index.android.js.map +1 -0
  53. package/build-ngc/angular/slider/index.d.ts +2 -0
  54. package/build-ngc/angular/slider/index.ios.d.ts +14 -0
  55. package/build-ngc/angular/slider/index.ios.js +29 -0
  56. package/build-ngc/angular/slider/index.ios.js.map +1 -0
  57. package/build-ngc/angular/slider/index.js +4 -0
  58. package/build-ngc/angular/slider/index.js.map +1 -0
  59. package/build-ngc/angular/slider/shared.d.ts +87 -0
  60. package/build-ngc/angular/slider/shared.js +427 -0
  61. package/build-ngc/angular/slider/shared.js.map +1 -0
  62. package/build-ngc/core/constants.d.ts +20 -0
  63. package/build-ngc/core/constants.js +41 -0
  64. package/build-ngc/core/constants.js.map +1 -0
  65. package/build-ngc/core/index.d.ts +7 -0
  66. package/build-ngc/core/index.js +12 -0
  67. package/build-ngc/core/index.js.map +1 -0
  68. package/build-ngc/core/render-slider.d.ts +11 -0
  69. package/build-ngc/core/render-slider.js +67 -0
  70. package/build-ngc/core/render-slider.js.map +1 -0
  71. package/build-ngc/core/render-steps-indicator.d.ts +28 -0
  72. package/build-ngc/core/render-steps-indicator.js +92 -0
  73. package/build-ngc/core/render-steps-indicator.js.map +1 -0
  74. package/build-ngc/core/slider-props.d.ts +52 -0
  75. package/build-ngc/core/slider-props.js +7 -0
  76. package/build-ngc/core/slider-props.js.map +1 -0
  77. package/build-ngc/core/slider-state.d.ts +15 -0
  78. package/build-ngc/core/slider-state.js +74 -0
  79. package/build-ngc/core/slider-state.js.map +1 -0
  80. package/build-ngc/register.d.ts +1 -0
  81. package/build-ngc/register.js +49 -0
  82. package/build-ngc/register.js.map +1 -0
  83. package/package.json +116 -0
  84. package/src/angular/index.ts +4 -0
  85. package/src/angular/slider/index.android.ts +25 -0
  86. package/src/angular/slider/index.ios.ts +26 -0
  87. package/src/angular/slider/index.ts +5 -0
  88. package/src/angular/slider/shared.ts +364 -0
  89. package/src/angular/slider/slider.test.ts +260 -0
  90. package/src/core/constants.ts +47 -0
  91. package/src/core/index.ts +46 -0
  92. package/src/core/render-slider.ts +89 -0
  93. package/src/core/render-steps-indicator.ts +154 -0
  94. package/src/core/slider-props.ts +83 -0
  95. package/src/core/slider-state.ts +112 -0
  96. package/src/react/index.ts +11 -0
  97. package/src/react/slider/index.android.ts +13 -0
  98. package/src/react/slider/index.ios.ts +14 -0
  99. package/src/react/slider/index.ts +4 -0
  100. package/src/react/slider/shared.ts +332 -0
  101. package/src/react/slider/slider.test.tsx +203 -0
  102. package/src/register.test.tsx +89 -0
  103. package/src/register.ts +59 -0
  104. package/src/vue/index.ts +11 -0
  105. package/src/vue/slider/index.android.ts +13 -0
  106. package/src/vue/slider/index.ios.ts +14 -0
  107. package/src/vue/slider/index.ts +5 -0
  108. package/src/vue/slider/shared.ts +383 -0
  109. package/src/vue/slider/slider.test.ts +184 -0
package/package.json ADDED
@@ -0,0 +1,116 @@
1
+ {
2
+ "name": "@symbiote-native/slider",
3
+ "version": "0.1.0",
4
+ "description": "Third-party native Slider view wrapped for SymbioteJS — one framework-agnostic core, built once and reachable from the React, Vue, and Angular adapters.",
5
+ "repository": {
6
+ "type": "git",
7
+ "url": "git+https://github.com/OneEyed1366/symbiote-native.git",
8
+ "directory": "packages/slider"
9
+ },
10
+ "homepage": "https://github.com/OneEyed1366/symbiote-native/tree/master/packages/slider#readme",
11
+ "bugs": {
12
+ "url": "https://github.com/OneEyed1366/symbiote-native/issues"
13
+ },
14
+ "author": "Andrey Prokopenko <psevdoproger@gmail.com>",
15
+ "type": "module",
16
+ "main": "./build/core/index.js",
17
+ "module": "./build/core/index.js",
18
+ "types": "./build/core/index.d.ts",
19
+ "exports": {
20
+ ".": {
21
+ "types": "./build/core/index.d.ts",
22
+ "default": "./build/core/index.js"
23
+ },
24
+ "./vue": {
25
+ "types": "./build/vue/index.d.ts",
26
+ "default": "./build/vue/index.js"
27
+ },
28
+ "./react": {
29
+ "types": "./build/react/index.d.ts",
30
+ "default": "./build/react/index.js"
31
+ },
32
+ "./angular": {
33
+ "types": "./build-ngc/angular/index.d.ts",
34
+ "react-native": "./build-ngc/angular/index.js",
35
+ "default": "./src/angular/index.ts"
36
+ }
37
+ },
38
+ "files": [
39
+ "src",
40
+ "build",
41
+ "build-ngc"
42
+ ],
43
+ "publishConfig": {
44
+ "access": "public"
45
+ },
46
+ "codegenConfig": {
47
+ "name": "RNCSlider",
48
+ "type": "components",
49
+ "jsSrcsDir": "node_modules/@react-native-community/slider/src",
50
+ "android": {
51
+ "javaPackageName": "com.reactnativecommunity.slider"
52
+ },
53
+ "ios": {
54
+ "componentProvider": {
55
+ "RNCSlider": "RNCSliderComponentView"
56
+ }
57
+ }
58
+ },
59
+ "dependencies": {
60
+ "@react-native-community/slider": "^5.2.0",
61
+ "@symbiote-native/components": "0.1.0",
62
+ "@symbiote-native/engine": "0.1.0"
63
+ },
64
+ "peerDependencies": {
65
+ "@angular/core": ">=20",
66
+ "@vue/runtime-core": "^3.5.13",
67
+ "react": ">=19.0.0",
68
+ "react-native": ">=0.86",
69
+ "vue": ">=3.5.0",
70
+ "@symbiote-native/angular": "0.1.0",
71
+ "@symbiote-native/react": "0.1.0",
72
+ "@symbiote-native/vue": "0.1.0"
73
+ },
74
+ "peerDependenciesMeta": {
75
+ "@symbiote-native/angular": {
76
+ "optional": true
77
+ },
78
+ "@symbiote-native/react": {
79
+ "optional": true
80
+ },
81
+ "@symbiote-native/vue": {
82
+ "optional": true
83
+ },
84
+ "@angular/core": {
85
+ "optional": true
86
+ },
87
+ "@vue/runtime-core": {
88
+ "optional": true
89
+ },
90
+ "react": {
91
+ "optional": true
92
+ },
93
+ "vue": {
94
+ "optional": true
95
+ }
96
+ },
97
+ "devDependencies": {
98
+ "@angular/compiler": "^22",
99
+ "@angular/compiler-cli": "^22",
100
+ "@angular/core": "^22",
101
+ "@types/node": "^26.0.0",
102
+ "@types/react": "^19.2.0",
103
+ "@vue/runtime-core": "^3.5.13",
104
+ "react": "19.2.3",
105
+ "typescript": "~6.0.0",
106
+ "@symbiote-native/angular": "0.1.0",
107
+ "@symbiote-native/react": "0.1.0",
108
+ "@symbiote-native/vue": "0.1.0",
109
+ "@symbiote-native/test-utils": "0.0.0"
110
+ },
111
+ "scripts": {
112
+ "typecheck": "tsc --build",
113
+ "ng:build": "ngc -p tsconfig.angular.json",
114
+ "format": "prettier --write \"src/**/*.{ts,tsx}\""
115
+ }
116
+ }
@@ -0,0 +1,4 @@
1
+ import '../register';
2
+
3
+ export { Slider } from './slider';
4
+ export type { ISliderProps } from './slider/shared';
@@ -0,0 +1,25 @@
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
+
4
+ import { ChangeDetectionStrategy, Component } from '@angular/core';
5
+ import { DescriptorOutlet } from '@symbiote-native/angular';
6
+ import { SliderBase } from './shared';
7
+ import { SLIDER_STEP_RESOLUTION_ANDROID } from '../../core';
8
+ export type { ISliderProps } from './shared';
9
+
10
+ const ANDROID_STEPS_CONTAINER_TOP = 0;
11
+
12
+ @Component({
13
+ selector: 'Slider',
14
+ standalone: true,
15
+ imports: [DescriptorOutlet],
16
+ changeDetection: ChangeDetectionStrategy.OnPush,
17
+ template: `<symbiote-descriptor-outlet [node]="descriptor" />`,
18
+ })
19
+ export class Slider extends SliderBase {
20
+ protected readonly platform = {
21
+ defaultStyle: {},
22
+ stepResolution: SLIDER_STEP_RESOLUTION_ANDROID,
23
+ stepsContainerTop: ANDROID_STEPS_CONTAINER_TOP,
24
+ };
25
+ }
@@ -0,0 +1,26 @@
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
+
5
+ import { ChangeDetectionStrategy, Component } from '@angular/core';
6
+ import { DescriptorOutlet } from '@symbiote-native/angular';
7
+ import { SliderBase } from './shared';
8
+ import { SLIDER_IOS_DEFAULT_HEIGHT, SLIDER_STEP_RESOLUTION_IOS } from '../../core';
9
+ export type { ISliderProps } from './shared';
10
+
11
+ const IOS_STEPS_CONTAINER_TOP = 10;
12
+
13
+ @Component({
14
+ selector: 'Slider',
15
+ standalone: true,
16
+ imports: [DescriptorOutlet],
17
+ changeDetection: ChangeDetectionStrategy.OnPush,
18
+ template: `<symbiote-descriptor-outlet [node]="descriptor" />`,
19
+ })
20
+ export class Slider extends SliderBase {
21
+ protected readonly platform = {
22
+ defaultStyle: { height: SLIDER_IOS_DEFAULT_HEIGHT },
23
+ stepResolution: SLIDER_STEP_RESOLUTION_IOS,
24
+ stepsContainerTop: IOS_STEPS_CONTAINER_TOP,
25
+ };
26
+ }
@@ -0,0 +1,5 @@
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). ADR 0026.
3
+
4
+ export { Slider } from './index.ios';
5
+ export type { ISliderProps } from './shared';
@@ -0,0 +1,364 @@
1
+ // Slider, the Angular lifecycle half. The logic (value/limit/disabled folds, the step-option
2
+ // layout) and the native render live in @symbiote-native/slider core, shared verbatim with the Vue
3
+ // adapter; here Angular supplies plain class fields (mirroring ActivityIndicatorBase/SwitchBase)
4
+ // plus real @Output() EventEmitters for the four native callbacks, and renders through
5
+ // DescriptorOutlet — the generic descriptorToAngular bridge (angular-adapter skill §6) — since
6
+ // this component has no imperative-ref need the Descriptor prop bag can't carry. The native
7
+ // RNCSlider view carries no symbiote metadata: the engine derives its events and color
8
+ // processors from the library's ViewConfig at runtime, registered by the side-effect import in
9
+ // ../../register (pulled in by the package barrel, NOT here, so this module and its tests stay
10
+ // free of the third-party spec). See CLAUDE.md <third_party_rn_packages_are_react_only> and
11
+ // ADR 0027.
12
+ //
13
+ // KNOWN GAP (deliberate, scoped by the task this module was built for): the custom `StepMarker`
14
+ // render slot (a per-adapter overlay element — a React FC, a Vue scoped slot) has no Angular
15
+ // equivalent yet. The natural analogue is a `@ContentChild(TemplateRef) stepMarker?:
16
+ // TemplateRef<IStepMarkerProps>` projected per step cell via `NgTemplateOutlet`, but wiring an
17
+ // embedded view per cell (constructing it, feeding its context, keeping it in step with
18
+ // DescriptorOutlet's imperative patch model, which has no notion of TemplateRefs) is
19
+ // disproportionate complexity for a path no real caller exercises today (examples/react/App.tsx's
20
+ // Slider usage has no step markers at all). Everything else — value/limits/disabled/colors/
21
+ // thumbImage/style/every event, and the DEFAULT numbered step indicator (`renderStepNumber`) — is
22
+ // fully implemented below; only the custom-marker overlay is unimplemented.
23
+
24
+ import {
25
+ ChangeDetectorRef,
26
+ Directive,
27
+ ElementRef,
28
+ EventEmitter,
29
+ inject,
30
+ Input,
31
+ Output,
32
+ type OnChanges,
33
+ } from '@angular/core';
34
+ import { anchorHostStyle } from '@symbiote-native/angular';
35
+ import { resolveAccessibilityProps } from '@symbiote-native/components';
36
+ import type {
37
+ IAccessibilityProps,
38
+ IAccessibilityStateValue,
39
+ IAriaProps,
40
+ IDescriptor,
41
+ } from '@symbiote-native/components';
42
+ import { dlog, type ISymbioteEvent } from '@symbiote-native/engine';
43
+ import {
44
+ sanitizeSliderValue,
45
+ resolveSliderDisabled,
46
+ resolveSliderAccessibilityState,
47
+ resolveSliderLowerLimit,
48
+ resolveSliderUpperLimit,
49
+ valueFromSliderEvent,
50
+ shouldRenderStepsIndicator,
51
+ resolveThumbTintColor,
52
+ shouldPassNativeThumbImage,
53
+ isInvalidLimitConfig,
54
+ computeStepOptions,
55
+ renderSlider,
56
+ renderStepsIndicator,
57
+ SLIDER_DEFAULT_MINIMUM_VALUE,
58
+ SLIDER_DEFAULT_MAXIMUM_VALUE,
59
+ SLIDER_DEFAULT_STEP,
60
+ SLIDER_ON_CHANGE,
61
+ SLIDER_ON_VALUE_CHANGE,
62
+ SLIDER_ON_SLIDING_START,
63
+ SLIDER_ON_SLIDING_COMPLETE,
64
+ SLIDER_ON_ACCESSIBILITY_ACTION,
65
+ type ISliderPlatform,
66
+ type ISliderProps as ISliderBaseProps,
67
+ type ISliderViewProps,
68
+ } from '../../core';
69
+
70
+ export type ISliderProps = Omit<
71
+ ISliderBaseProps,
72
+ 'onValueChange' | 'onSlidingStart' | 'onSlidingComplete' | 'onAccessibilityAction'
73
+ >;
74
+
75
+ function isRecord(value: unknown): value is Record<string, unknown> {
76
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
77
+ }
78
+
79
+ // Narrows anchorHostStyle's `unknown` (it reads an opaque engine prop bag) to the shape
80
+ // ISliderProps['style'] actually declares — the same runtime guard the Angular adapter's
81
+ // ActivityIndicator uses for the identical reason: a style value is structurally opaque at the
82
+ // type level, so this only rules out non-objects, never validates individual style keys.
83
+ function asStyle(value: unknown): ISliderProps['style'] {
84
+ return typeof value === 'object' && value !== null ? value : undefined;
85
+ }
86
+
87
+ @Directive()
88
+ export abstract class SliderBase implements ISliderProps, OnChanges {
89
+ @Input() value?: number;
90
+ @Input() minimumValue?: number;
91
+ @Input() maximumValue?: number;
92
+ @Input() step?: number;
93
+ @Input() lowerLimit?: number;
94
+ @Input() upperLimit?: number;
95
+ @Input() minimumTrackTintColor?: ISliderProps['minimumTrackTintColor'];
96
+ @Input() maximumTrackTintColor?: ISliderProps['maximumTrackTintColor'];
97
+ @Input() thumbTintColor?: ISliderProps['thumbTintColor'];
98
+ @Input() disabled?: boolean;
99
+ @Input() inverted?: boolean;
100
+ @Input() tapToSeek?: boolean;
101
+ @Input() vertical?: boolean;
102
+ @Input() thumbImage?: ISliderProps['thumbImage'];
103
+ @Input() minimumTrackImage?: ISliderProps['minimumTrackImage'];
104
+ @Input() maximumTrackImage?: ISliderProps['maximumTrackImage'];
105
+ @Input() trackImage?: ISliderProps['trackImage'];
106
+ @Input() thumbSize?: number;
107
+ @Input() accessibilityUnits?: string;
108
+ @Input() accessibilityIncrements?: readonly string[];
109
+ @Input() renderStepNumber?: boolean;
110
+ @Input() testID?: string;
111
+ @Input() style?: ISliderProps['style'];
112
+
113
+ @Input() nativeID?: string;
114
+ @Input() accessible?: boolean;
115
+ @Input() accessibilityLabel?: string;
116
+ @Input() accessibilityHint?: string;
117
+ @Input() accessibilityRole?: IAccessibilityProps['accessibilityRole'];
118
+ @Input() accessibilityState?: IAccessibilityStateValue;
119
+ @Input() accessibilityValue?: IAccessibilityProps['accessibilityValue'];
120
+ @Input() accessibilityActions?: IAccessibilityProps['accessibilityActions'];
121
+ @Input() accessibilityLabelledBy?: string | string[];
122
+ @Input() importantForAccessibility?: IAccessibilityProps['importantForAccessibility'];
123
+ @Input() accessibilityLiveRegion?: IAccessibilityProps['accessibilityLiveRegion'];
124
+ @Input() screenReaderFocusable?: boolean;
125
+ @Input() accessibilityViewIsModal?: boolean;
126
+ @Input() accessibilityElementsHidden?: boolean;
127
+ @Input() accessibilityIgnoresInvertColors?: boolean;
128
+ @Input() accessibilityLanguage?: string;
129
+ @Input() accessibilityRespondsToUserInteraction?: boolean;
130
+ @Input() accessibilityShowsLargeContentViewer?: boolean;
131
+ @Input() accessibilityLargeContentTitle?: string;
132
+ @Input() onAccessibilityTap?: (event: ISymbioteEvent) => void;
133
+ @Input() onMagicTap?: (event: ISymbioteEvent) => void;
134
+ @Input() onAccessibilityEscape?: (event: ISymbioteEvent) => void;
135
+
136
+ @Input() role?: IAriaProps['role'];
137
+ @Input('aria-label') ariaLabel?: string;
138
+ @Input('aria-labelledby') ariaLabelledBy?: string;
139
+ @Input('aria-live') ariaLive?: IAriaProps['aria-live'];
140
+ @Input('aria-hidden') ariaHidden?: boolean;
141
+ @Input('aria-busy') ariaBusy?: boolean;
142
+ @Input('aria-checked') ariaChecked?: boolean | 'mixed';
143
+ @Input('aria-disabled') ariaDisabled?: boolean;
144
+ @Input('aria-expanded') ariaExpanded?: boolean;
145
+ @Input('aria-selected') ariaSelected?: boolean;
146
+ @Input('aria-modal') ariaModal?: boolean;
147
+ @Input('aria-valuemax') ariaValueMax?: number;
148
+ @Input('aria-valuemin') ariaValueMin?: number;
149
+ @Input('aria-valuenow') ariaValueNow?: number;
150
+ @Input('aria-valuetext') ariaValueText?: string;
151
+
152
+ // The four native callbacks, as real EventEmitters — no v-model-style two-way twin (that was
153
+ // Vue-specific v-model sugar; Angular has no equivalent concept here).
154
+ @Output() readonly valueChange = new EventEmitter<number>();
155
+ @Output() readonly slidingStart = new EventEmitter<number>();
156
+ @Output() readonly slidingComplete = new EventEmitter<number>();
157
+ @Output() readonly accessibilityAction = new EventEmitter<ISymbioteEvent>();
158
+
159
+ protected abstract readonly platform: ISliderPlatform;
160
+
161
+ private readonly changeDetector = inject(ChangeDetectorRef);
162
+ // This component's OWN host — the non-painting anchor `class="..."` at the use site resolves
163
+ // onto (see anchorHostStyle's doc comment, @symbiote-native/angular) — NOT the descriptor-outlet-
164
+ // rendered wrapper the `descriptor` getter below builds. Merged FIRST so an explicit `style`
165
+ // @Input still wins (flattenStyle's later-wins collapse), mirroring every other composed
166
+ // component's anchor merge.
167
+ private readonly elementRef = inject(ElementRef);
168
+
169
+ // The value native last reported, kept only to mark the active step in the indicator. Not the
170
+ // controlled value — the slider is uncontrolled during a drag (no snap-back), mirroring the
171
+ // Vue lifecycle's `reportedValue` ref.
172
+ private reportedValue: number | undefined;
173
+ // The measured wrapper width the step indicator lays out against; 0 until the first layout.
174
+ private width = 0;
175
+
176
+ ngOnChanges(): void {
177
+ // No fold needed here: `descriptor` is a getter re-evaluated on every OnPush check the
178
+ // template's `[node]="descriptor"` binding triggers, so an @Input change already recomputes
179
+ // the render on its own. OnChanges exists so this class satisfies the same lifecycle contract
180
+ // as the rest of the input-driven components in this adapter.
181
+ }
182
+
183
+ protected readonly handleValueChange = (event: ISymbioteEvent): void => {
184
+ const value = valueFromSliderEvent(event);
185
+ if (value === undefined) return;
186
+ this.reportedValue = value;
187
+ this.valueChange.emit(value);
188
+ };
189
+
190
+ protected readonly handleSlidingStart = (event: ISymbioteEvent): void => {
191
+ const value = valueFromSliderEvent(event);
192
+ if (value !== undefined) this.slidingStart.emit(value);
193
+ };
194
+
195
+ protected readonly handleSlidingComplete = (event: ISymbioteEvent): void => {
196
+ const value = valueFromSliderEvent(event);
197
+ if (value !== undefined) this.slidingComplete.emit(value);
198
+ };
199
+
200
+ protected readonly handleAccessibilityAction = (event: ISymbioteEvent): void => {
201
+ this.accessibilityAction.emit(event);
202
+ };
203
+
204
+ // Native onLayout fires through the engine's event dispatch (Renderer2.listen), OUTSIDE
205
+ // Angular's zoneless change-detection notification — the same async-commit-timing shape the
206
+ // angular-adapter skill (§5) documents and the sticky-header component already works around
207
+ // (`ChangeDetectorRef.markForCheck()`). Mutating `width` alone would never repaint the step
208
+ // indicator, since nothing would tell the OnPush view it is dirty.
209
+ protected readonly handleLayout = (event: ISymbioteEvent): void => {
210
+ const layout = event.nativeEvent.layout;
211
+ if (isRecord(layout) && typeof layout.width === 'number') {
212
+ this.width = layout.width;
213
+ this.changeDetector.markForCheck();
214
+ }
215
+ };
216
+
217
+ private inputProps(): ISliderProps {
218
+ return {
219
+ value: this.value,
220
+ minimumValue: this.minimumValue,
221
+ maximumValue: this.maximumValue,
222
+ step: this.step,
223
+ lowerLimit: this.lowerLimit,
224
+ upperLimit: this.upperLimit,
225
+ minimumTrackTintColor: this.minimumTrackTintColor,
226
+ maximumTrackTintColor: this.maximumTrackTintColor,
227
+ thumbTintColor: this.thumbTintColor,
228
+ disabled: this.disabled,
229
+ inverted: this.inverted,
230
+ tapToSeek: this.tapToSeek,
231
+ vertical: this.vertical,
232
+ thumbImage: this.thumbImage,
233
+ minimumTrackImage: this.minimumTrackImage,
234
+ maximumTrackImage: this.maximumTrackImage,
235
+ trackImage: this.trackImage,
236
+ thumbSize: this.thumbSize,
237
+ accessibilityUnits: this.accessibilityUnits,
238
+ accessibilityIncrements: this.accessibilityIncrements,
239
+ renderStepNumber: this.renderStepNumber,
240
+ testID: this.testID,
241
+ style: [asStyle(anchorHostStyle(this.elementRef)), this.style],
242
+ nativeID: this.nativeID,
243
+ accessible: this.accessible,
244
+ accessibilityLabel: this.accessibilityLabel,
245
+ accessibilityHint: this.accessibilityHint,
246
+ accessibilityRole: this.accessibilityRole,
247
+ accessibilityState: this.accessibilityState,
248
+ accessibilityValue: this.accessibilityValue,
249
+ accessibilityActions: this.accessibilityActions,
250
+ accessibilityLabelledBy: this.accessibilityLabelledBy,
251
+ importantForAccessibility: this.importantForAccessibility,
252
+ accessibilityLiveRegion: this.accessibilityLiveRegion,
253
+ screenReaderFocusable: this.screenReaderFocusable,
254
+ accessibilityViewIsModal: this.accessibilityViewIsModal,
255
+ accessibilityElementsHidden: this.accessibilityElementsHidden,
256
+ accessibilityIgnoresInvertColors: this.accessibilityIgnoresInvertColors,
257
+ accessibilityLanguage: this.accessibilityLanguage,
258
+ accessibilityRespondsToUserInteraction: this.accessibilityRespondsToUserInteraction,
259
+ accessibilityShowsLargeContentViewer: this.accessibilityShowsLargeContentViewer,
260
+ accessibilityLargeContentTitle: this.accessibilityLargeContentTitle,
261
+ onAccessibilityTap: this.onAccessibilityTap,
262
+ onMagicTap: this.onMagicTap,
263
+ onAccessibilityEscape: this.onAccessibilityEscape,
264
+ role: this.role,
265
+ 'aria-label': this.ariaLabel,
266
+ 'aria-labelledby': this.ariaLabelledBy,
267
+ 'aria-live': this.ariaLive,
268
+ 'aria-hidden': this.ariaHidden,
269
+ 'aria-busy': this.ariaBusy,
270
+ 'aria-checked': this.ariaChecked,
271
+ 'aria-disabled': this.ariaDisabled,
272
+ 'aria-expanded': this.ariaExpanded,
273
+ 'aria-selected': this.ariaSelected,
274
+ 'aria-modal': this.ariaModal,
275
+ 'aria-valuemax': this.ariaValueMax,
276
+ 'aria-valuemin': this.ariaValueMin,
277
+ 'aria-valuenow': this.ariaValueNow,
278
+ 'aria-valuetext': this.ariaValueText,
279
+ };
280
+ }
281
+
282
+ get descriptor(): IDescriptor {
283
+ const props = resolveAccessibilityProps<ISliderProps>(this.inputProps());
284
+ const {
285
+ value,
286
+ minimumValue: minimumValueInput,
287
+ maximumValue: maximumValueInput,
288
+ step: stepInput,
289
+ lowerLimit: lowerLimitInput,
290
+ upperLimit: upperLimitInput,
291
+ disabled,
292
+ inverted: invertedInput,
293
+ thumbTintColor,
294
+ thumbImage,
295
+ accessibilityState,
296
+ renderStepNumber,
297
+ style,
298
+ ...passthrough
299
+ } = props;
300
+
301
+ const minimumValue = minimumValueInput ?? SLIDER_DEFAULT_MINIMUM_VALUE;
302
+ const maximumValue = maximumValueInput ?? SLIDER_DEFAULT_MAXIMUM_VALUE;
303
+ const step = stepInput ?? SLIDER_DEFAULT_STEP;
304
+ const lowerLimit = resolveSliderLowerLimit(lowerLimitInput);
305
+ const upperLimit = resolveSliderUpperLimit(upperLimitInput);
306
+ if (isInvalidLimitConfig(lowerLimit, upperLimit)) {
307
+ dlog('Slider: lowerLimit must be smaller than upperLimit');
308
+ }
309
+ const inverted = invertedInput ?? false;
310
+ const hasThumbImage = thumbImage !== undefined;
311
+ // No custom-StepMarker slot on Angular yet (see the module doc comment above) — showSteps
312
+ // only ever considers renderStepNumber.
313
+ const showSteps = shouldRenderStepsIndicator(false, renderStepNumber);
314
+ const nativeThumbImage = shouldPassNativeThumbImage(false, hasThumbImage)
315
+ ? thumbImage
316
+ : undefined;
317
+
318
+ const view: ISliderViewProps = {
319
+ value: sanitizeSliderValue(value),
320
+ minimumValue,
321
+ maximumValue,
322
+ step,
323
+ lowerLimit,
324
+ upperLimit,
325
+ disabled: resolveSliderDisabled(disabled, accessibilityState),
326
+ inverted,
327
+ thumbTintColor: resolveThumbTintColor(thumbTintColor, false, hasThumbImage),
328
+ thumbImage: nativeThumbImage,
329
+ accessibilityState: resolveSliderAccessibilityState(disabled, accessibilityState),
330
+ width: this.width,
331
+ style,
332
+ passthrough: {
333
+ ...passthrough,
334
+ [SLIDER_ON_CHANGE]: this.handleValueChange,
335
+ [SLIDER_ON_VALUE_CHANGE]: this.handleValueChange,
336
+ [SLIDER_ON_SLIDING_START]: this.handleSlidingStart,
337
+ [SLIDER_ON_SLIDING_COMPLETE]: this.handleSlidingComplete,
338
+ [SLIDER_ON_ACCESSIBILITY_ACTION]: this.handleAccessibilityAction,
339
+ },
340
+ };
341
+
342
+ if (!showSteps) {
343
+ return renderSlider(view, this.platform, { onLayout: this.handleLayout });
344
+ }
345
+
346
+ const options = computeStepOptions(
347
+ minimumValue,
348
+ maximumValue,
349
+ step,
350
+ this.platform.stepResolution,
351
+ );
352
+ const currentValue = this.reportedValue ?? view.value ?? minimumValue;
353
+ const steps = renderStepsIndicator({
354
+ options,
355
+ currentValue,
356
+ width: this.width,
357
+ renderStepNumber: renderStepNumber === true,
358
+ thumbImage,
359
+ inverted,
360
+ platform: this.platform,
361
+ });
362
+ return renderSlider(view, this.platform, { steps, onLayout: this.handleLayout });
363
+ }
364
+ }