ember-primitives 0.59.1 → 0.60.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.
@@ -0,0 +1,281 @@
1
+ import "./slider.css";
2
+
3
+ import Component from "@glimmer/component";
4
+ import { hash } from "@ember/helper";
5
+ import { on } from "@ember/modifier";
6
+
7
+ import { SliderStore, type SliderThumb, type StyleString } from "./slider/store.ts";
8
+
9
+ import type { TOC } from "@ember/component/template-only";
10
+ import type Owner from "@ember/owner";
11
+ import type { WithBoundArgs } from "@glint/template";
12
+
13
+ export type { SliderThumb };
14
+
15
+ export interface Signature {
16
+ Element: HTMLSpanElement;
17
+ Args: {
18
+ /**
19
+ * The current value(s) of the slider.
20
+ * For single value slider, pass a single number.
21
+ * For range slider, pass an array of numbers [min, max].
22
+ */
23
+ value?: number | number[];
24
+ /**
25
+ * The minimum value of the slider.
26
+ * Defaults to 0.
27
+ */
28
+ min?: number;
29
+ /**
30
+ * The maximum value of the slider.
31
+ * Defaults to 100.
32
+ */
33
+ max?: number;
34
+ /**
35
+ * The stepping interval.
36
+ *
37
+ * When passed a number, the slider moves in fixed increments.
38
+ * When passed an array of numbers, the slider snaps to those discrete values.
39
+ * Defaults to 1.
40
+ */
41
+ step?: number | number[];
42
+ /**
43
+ * The orientation of the slider.
44
+ * Defaults to 'horizontal'.
45
+ */
46
+ orientation?: "horizontal" | "vertical";
47
+ /**
48
+ * Whether the slider is disabled.
49
+ */
50
+ disabled?: boolean;
51
+ /**
52
+ * Callback when the value changes during dragging.
53
+ */
54
+ onValueChange?: (value: number | number[]) => void;
55
+ /**
56
+ * Callback when the value is committed (after dragging ends).
57
+ */
58
+ onValueCommit?: (value: number | number[]) => void;
59
+ };
60
+ Blocks: {
61
+ default: [
62
+ {
63
+ /**
64
+ * The track element - the rail along which the thumb moves
65
+ */
66
+ Track: typeof Track;
67
+ /**
68
+ * The range element - the filled portion of the track
69
+ */
70
+ Range: WithBoundArgs<typeof Range, "rangeStyle">;
71
+ /**
72
+ * The thumb element - the draggable handle(s)
73
+ */
74
+ Thumb: WithBoundArgs<typeof ThumbComponent, "store">;
75
+ /**
76
+ * The current value(s)
77
+ */
78
+ values: number[];
79
+
80
+ /**
81
+ * The tick values, if any.
82
+ */
83
+ tickValues: number[] | null;
84
+
85
+ /**
86
+ * A stable list of thumbs to iterate over.
87
+ * Prefer this over iterating `values` directly to avoid DOM churn during dragging.
88
+ */
89
+ thumbs: SliderThumb[];
90
+ /**
91
+ * The minimum value
92
+ */
93
+ min: number;
94
+ /**
95
+ * The maximum value
96
+ */
97
+ max: number;
98
+ /**
99
+ * The step value
100
+ */
101
+ step: number;
102
+ },
103
+ ];
104
+ };
105
+ }
106
+
107
+ interface TrackSignature {
108
+ Element: HTMLSpanElement;
109
+ Args: Record<string, never>;
110
+ Blocks: {
111
+ default: [];
112
+ };
113
+ }
114
+
115
+ const Track: TOC<TrackSignature> = <template>
116
+ <span ...attributes class="ember-primitives__slider__track">
117
+ {{yield}}
118
+ </span>
119
+ </template>;
120
+
121
+ interface RangeSignature {
122
+ Element: HTMLSpanElement;
123
+ Args: {
124
+ rangeStyle: StyleString;
125
+ };
126
+ }
127
+
128
+ const Range: TOC<RangeSignature> = <template>
129
+ <span ...attributes class="ember-primitives__slider__range" style={{@rangeStyle}} />
130
+ </template>;
131
+
132
+ class ThumbComponent extends Component<{
133
+ /**
134
+ * `...attributes` land on the invisible native input, which is the
135
+ * interactive element (pass `aria-label` / `aria-labelledby` here).
136
+ */
137
+ Element: HTMLInputElement;
138
+ Args: {
139
+ store: SliderStore;
140
+ /**
141
+ * Optional convenience: pass the full thumb object instead of `@value` + `@index`.
142
+ */
143
+ thumb?: SliderThumb;
144
+ value?: number;
145
+ index?: number;
146
+ };
147
+ Blocks: {
148
+ /**
149
+ * Rendered inside the visual thumb — useful for tooltips / value labels.
150
+ */
151
+ default: [];
152
+ };
153
+ }> {
154
+ get index(): number {
155
+ return this.args.thumb?.index ?? this.args.index ?? 0;
156
+ }
157
+
158
+ get value(): number {
159
+ // When using tick values, the `input` needs the internal index.
160
+ return this.args.thumb?.inputValue ?? this.args.value ?? this.args.store.internalMin;
161
+ }
162
+
163
+ get isActive(): boolean {
164
+ return this.args.store.activeThumbIndex === this.index;
165
+ }
166
+
167
+ get positionStyle() {
168
+ const percent = this.args.thumb?.percent ?? this.args.store.thumbPercents[this.index] ?? 0;
169
+
170
+ return this.args.store.thumbPositionStyle(percent);
171
+ }
172
+
173
+ private readValue(event: Event): number {
174
+ // In docs live previews the component may run in an iframe/shadow realm,
175
+ // where `instanceof HTMLInputElement` is not reliable. `currentTarget` is
176
+ // the element the handler is attached to.
177
+ const el = event.currentTarget as { value?: string } | null;
178
+ const raw = el?.value;
179
+ const parsed = raw === undefined ? NaN : Number.parseFloat(raw);
180
+
181
+ return Number.isFinite(parsed) ? parsed : this.value;
182
+ }
183
+
184
+ private onInput = (event: Event) => {
185
+ this.args.store.handleThumbActivate(this.index);
186
+ this.args.store.handleThumbInput(this.index, this.readValue(event));
187
+ };
188
+
189
+ private onChange = (event: Event) => {
190
+ this.args.store.handleThumbActivate(this.index);
191
+ this.args.store.handleThumbChange(this.index, this.readValue(event));
192
+ };
193
+
194
+ private onPointerUp = () => {
195
+ this.args.store.handleThumbActivate(this.index);
196
+ };
197
+
198
+ private onGotPointerCapture = () => {
199
+ this.args.store.handleThumbActivate(this.index);
200
+ };
201
+
202
+ private onFocus = () => {
203
+ this.args.store.handleThumbActivate(this.index);
204
+ };
205
+
206
+ <template>
207
+ <input
208
+ ...attributes
209
+ class="ember-primitives__slider__thumb-input"
210
+ type="range"
211
+ min={{@store.internalMin}}
212
+ max={{@store.internalMax}}
213
+ step={{@store.internalStep}}
214
+ value={{this.value}}
215
+ disabled={{@store.disabled}}
216
+ data-active={{if this.isActive ""}}
217
+ {{on "gotpointercapture" this.onGotPointerCapture}}
218
+ {{on "pointerup" this.onPointerUp}}
219
+ {{on "focus" this.onFocus}}
220
+ {{on "input" this.onInput}}
221
+ {{on "change" this.onChange}}
222
+ />
223
+ <span
224
+ class="ember-primitives__slider__thumb"
225
+ style={{this.positionStyle}}
226
+ data-active={{if this.isActive ""}}
227
+ data-disabled={{if @store.disabled ""}}
228
+ aria-hidden="true"
229
+ >{{yield}}</span>
230
+ </template>
231
+ }
232
+
233
+ export class Slider extends Component<Signature> {
234
+ store: SliderStore;
235
+
236
+ constructor(owner: Owner, args: Signature["Args"]) {
237
+ super(owner, args);
238
+
239
+ this.store = new SliderStore(() => this.args);
240
+ }
241
+
242
+ <template>
243
+ <span
244
+ ...attributes
245
+ class="ember-primitives__slider"
246
+ data-orientation={{this.store.orientation}}
247
+ data-disabled={{if this.store.disabled ""}}
248
+ data-multi={{if this.store.isMulti ""}}
249
+ >
250
+ {{#if (has-block)}}
251
+ {{yield
252
+ (hash
253
+ Track=Track
254
+ Range=(component Range rangeStyle=this.store.rangeStyle)
255
+ Thumb=(component ThumbComponent store=this.store)
256
+ values=this.store.values
257
+ tickValues=this.store.tickValues
258
+ thumbs=this.store.thumbs
259
+ min=this.store.min
260
+ max=this.store.max
261
+ step=this.store.step
262
+ )
263
+ }}
264
+ {{else}}
265
+ <Track>
266
+ <Range @rangeStyle={{this.store.rangeStyle}} />
267
+
268
+ {{#each this.store.thumbs as |thumb|}}
269
+ <ThumbComponent
270
+ @store={{this.store}}
271
+ @thumb={{thumb}}
272
+ aria-label={{this.store.defaultThumbLabel thumb.index}}
273
+ />
274
+ {{/each}}
275
+ </Track>
276
+ {{/if}}
277
+ </span>
278
+ </template>
279
+ }
280
+
281
+ export default Slider;
package/src/index.ts CHANGED
@@ -39,6 +39,7 @@ export { Rating } from './components/rating.gts';
39
39
  export { Scroller } from './components/scroller.gts';
40
40
  export { Separator } from './components/separator.gts';
41
41
  export { Shadowed } from './components/shadowed.gts';
42
+ export { Slider } from './components/slider.gts';
42
43
  export { Switch } from './components/switch.gts';
43
44
  export { Toggle } from './components/toggle.gts';
44
45
  export { ToggleGroup } from './components/toggle-group.gts';