@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.
- package/LICENSE +21 -0
- package/README.md +90 -0
- package/build/angular/index.d.ts +3 -0
- package/build/angular/index.js +2 -0
- package/build/angular/slider/index.android.d.ts +9 -0
- package/build/angular/slider/index.android.js +71 -0
- package/build/angular/slider/index.d.ts +2 -0
- package/build/angular/slider/index.ios.d.ts +11 -0
- package/build/angular/slider/index.ios.js +72 -0
- package/build/angular/slider/index.js +3 -0
- package/build/angular/slider/shared.d.ts +84 -0
- package/build/angular/slider/shared.js +648 -0
- package/build/core/constants.d.ts +20 -0
- package/build/core/constants.js +40 -0
- package/build/core/index.d.ts +7 -0
- package/build/core/index.js +11 -0
- package/build/core/render-slider.d.ts +11 -0
- package/build/core/render-slider.js +66 -0
- package/build/core/render-steps-indicator.d.ts +28 -0
- package/build/core/render-steps-indicator.js +91 -0
- package/build/core/slider-props.d.ts +52 -0
- package/build/core/slider-props.js +6 -0
- package/build/core/slider-state.d.ts +15 -0
- package/build/core/slider-state.js +73 -0
- package/build/react/index.d.ts +4 -0
- package/build/react/index.js +7 -0
- package/build/react/slider/index.android.d.ts +1 -0
- package/build/react/slider/index.android.js +10 -0
- package/build/react/slider/index.d.ts +1 -0
- package/build/react/slider/index.ios.d.ts +1 -0
- package/build/react/slider/index.ios.js +11 -0
- package/build/react/slider/index.js +3 -0
- package/build/react/slider/shared.d.ts +10 -0
- package/build/react/slider/shared.js +175 -0
- package/build/register.d.ts +1 -0
- package/build/register.js +48 -0
- package/build/vue/index.d.ts +3 -0
- package/build/vue/index.js +8 -0
- package/build/vue/slider/index.android.d.ts +18 -0
- package/build/vue/slider/index.android.js +10 -0
- package/build/vue/slider/index.d.ts +2 -0
- package/build/vue/slider/index.ios.d.ts +18 -0
- package/build/vue/slider/index.ios.js +11 -0
- package/build/vue/slider/index.js +3 -0
- package/build/vue/slider/shared.d.ts +26 -0
- package/build/vue/slider/shared.js +254 -0
- package/build-ngc/angular/index.d.ts +3 -0
- package/build-ngc/angular/index.js +3 -0
- package/build-ngc/angular/index.js.map +1 -0
- package/build-ngc/angular/slider/index.android.d.ts +12 -0
- package/build-ngc/angular/slider/index.android.js +28 -0
- package/build-ngc/angular/slider/index.android.js.map +1 -0
- package/build-ngc/angular/slider/index.d.ts +2 -0
- package/build-ngc/angular/slider/index.ios.d.ts +14 -0
- package/build-ngc/angular/slider/index.ios.js +29 -0
- package/build-ngc/angular/slider/index.ios.js.map +1 -0
- package/build-ngc/angular/slider/index.js +4 -0
- package/build-ngc/angular/slider/index.js.map +1 -0
- package/build-ngc/angular/slider/shared.d.ts +87 -0
- package/build-ngc/angular/slider/shared.js +427 -0
- package/build-ngc/angular/slider/shared.js.map +1 -0
- package/build-ngc/core/constants.d.ts +20 -0
- package/build-ngc/core/constants.js +41 -0
- package/build-ngc/core/constants.js.map +1 -0
- package/build-ngc/core/index.d.ts +7 -0
- package/build-ngc/core/index.js +12 -0
- package/build-ngc/core/index.js.map +1 -0
- package/build-ngc/core/render-slider.d.ts +11 -0
- package/build-ngc/core/render-slider.js +67 -0
- package/build-ngc/core/render-slider.js.map +1 -0
- package/build-ngc/core/render-steps-indicator.d.ts +28 -0
- package/build-ngc/core/render-steps-indicator.js +92 -0
- package/build-ngc/core/render-steps-indicator.js.map +1 -0
- package/build-ngc/core/slider-props.d.ts +52 -0
- package/build-ngc/core/slider-props.js +7 -0
- package/build-ngc/core/slider-props.js.map +1 -0
- package/build-ngc/core/slider-state.d.ts +15 -0
- package/build-ngc/core/slider-state.js +74 -0
- package/build-ngc/core/slider-state.js.map +1 -0
- package/build-ngc/register.d.ts +1 -0
- package/build-ngc/register.js +49 -0
- package/build-ngc/register.js.map +1 -0
- package/package.json +116 -0
- package/src/angular/index.ts +4 -0
- package/src/angular/slider/index.android.ts +25 -0
- package/src/angular/slider/index.ios.ts +26 -0
- package/src/angular/slider/index.ts +5 -0
- package/src/angular/slider/shared.ts +364 -0
- package/src/angular/slider/slider.test.ts +260 -0
- package/src/core/constants.ts +47 -0
- package/src/core/index.ts +46 -0
- package/src/core/render-slider.ts +89 -0
- package/src/core/render-steps-indicator.ts +154 -0
- package/src/core/slider-props.ts +83 -0
- package/src/core/slider-state.ts +112 -0
- package/src/react/index.ts +11 -0
- package/src/react/slider/index.android.ts +13 -0
- package/src/react/slider/index.ios.ts +14 -0
- package/src/react/slider/index.ts +4 -0
- package/src/react/slider/shared.ts +332 -0
- package/src/react/slider/slider.test.tsx +203 -0
- package/src/register.test.tsx +89 -0
- package/src/register.ts +59 -0
- package/src/vue/index.ts +11 -0
- package/src/vue/slider/index.android.ts +13 -0
- package/src/vue/slider/index.ios.ts +14 -0
- package/src/vue/slider/index.ts +5 -0
- package/src/vue/slider/shared.ts +383 -0
- 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,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
|
+
}
|