@geoffcox/sterling-svelte 0.0.3 → 0.0.4

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,360 @@
1
+ <script>import { createEventDispatcher, onMount, tick } from "svelte";
2
+ import { v4 as uuidv4 } from "uuid";
3
+ import { computePosition, flip, offset, shift, autoUpdate } from "@floating-ui/dom";
4
+ import { clickOutside } from "../clickOutside";
5
+ import List from "../lists/List.svelte";
6
+ export let disabled = false;
7
+ export let items = [];
8
+ export let open = false;
9
+ export let selectedIndex = 0;
10
+ export let selectedItem = void 0;
11
+ $: {
12
+ selectedItem = items[selectedIndex];
13
+ }
14
+ let prevOpen = false;
15
+ let pendingSelectedIndex = selectedIndex;
16
+ let selectRef;
17
+ let popupRef;
18
+ let listRef;
19
+ const popupId = uuidv4();
20
+ let popupPosition = {
21
+ x: void 0,
22
+ y: void 0
23
+ };
24
+ const dispatch = createEventDispatcher();
25
+ const raiseItemSelected = (index) => {
26
+ dispatch("itemSelected", { index, item: items[index] });
27
+ };
28
+ const raiseItemSelectPending = (index) => {
29
+ dispatch("itemSelectPending", { index, item: items[index] });
30
+ };
31
+ $:
32
+ selectedIndex, () => {
33
+ pendingSelectedIndex = selectedIndex;
34
+ };
35
+ $: {
36
+ raiseItemSelected(selectedIndex);
37
+ }
38
+ $: {
39
+ console.log("raise pendingSelectedIndex changed");
40
+ raiseItemSelectPending(pendingSelectedIndex);
41
+ }
42
+ $: {
43
+ if (open && !prevOpen) {
44
+ prevOpen = true;
45
+ tick().then(() => listRef?.focusSelectedItem());
46
+ } else if (prevOpen) {
47
+ prevOpen = false;
48
+ tick().then(() => selectRef?.focus());
49
+ }
50
+ }
51
+ let mounted = false;
52
+ onMount(() => {
53
+ mounted = true;
54
+ const cleanup = autoUpdate(selectRef, popupRef, async () => {
55
+ const { x, y } = await computePosition(selectRef, popupRef, {
56
+ placement: "bottom-end",
57
+ middleware: [offset({ mainAxis: 2 }), flip(), shift({ padding: 0 })]
58
+ });
59
+ if (open) {
60
+ popupPosition = { x, y };
61
+ }
62
+ });
63
+ return cleanup;
64
+ });
65
+ const onSelectClick = (event) => {
66
+ if (!disabled) {
67
+ open = !open;
68
+ event.preventDefault();
69
+ event.stopPropagation();
70
+ }
71
+ };
72
+ const onSelectKeydown = (event) => {
73
+ if (!disabled && !event.ctrlKey && !event.shiftKey && !event.altKey && !event.metaKey) {
74
+ switch (event.key) {
75
+ case " ":
76
+ {
77
+ open = !open;
78
+ event.preventDefault();
79
+ event.stopPropagation();
80
+ }
81
+ break;
82
+ case "ArrowUp":
83
+ {
84
+ listRef.selectPreviousItem();
85
+ event.preventDefault();
86
+ event.stopPropagation();
87
+ }
88
+ break;
89
+ case "ArrowDown":
90
+ {
91
+ listRef.selectNextItem();
92
+ event.preventDefault();
93
+ event.stopPropagation();
94
+ }
95
+ break;
96
+ }
97
+ }
98
+ };
99
+ const onListKeydown = (event) => {
100
+ if (!disabled && !event.ctrlKey && !event.shiftKey && !event.altKey && !event.metaKey) {
101
+ switch (event.key) {
102
+ case "Enter":
103
+ {
104
+ selectedIndex = pendingSelectedIndex;
105
+ open = !open;
106
+ event.preventDefault();
107
+ event.stopPropagation();
108
+ }
109
+ break;
110
+ case "Escape":
111
+ {
112
+ pendingSelectedIndex = selectedIndex;
113
+ open = !open;
114
+ event.preventDefault();
115
+ event.stopPropagation();
116
+ }
117
+ break;
118
+ }
119
+ }
120
+ };
121
+ const onListClick = (event) => {
122
+ if (!disabled && !event.ctrlKey && !event.shiftKey && !event.altKey && !event.metaKey) {
123
+ selectedIndex = pendingSelectedIndex;
124
+ open = false;
125
+ event.preventDefault();
126
+ event.stopPropagation();
127
+ }
128
+ };
129
+ const onPendingItemSelected = (event) => {
130
+ pendingSelectedIndex = event.detail.index;
131
+ console.log("pendingSelectedIndex changed");
132
+ if (!open) {
133
+ selectedIndex = pendingSelectedIndex;
134
+ }
135
+ };
136
+ </script>
137
+
138
+ <!--
139
+ @component
140
+ Pops up a list of items when clicked.
141
+ A single item can be selected and is displayed as the value.
142
+ -->
143
+ <div
144
+ bind:this={selectRef}
145
+ use:clickOutside
146
+ aria-controls={popupId}
147
+ aria-haspopup="listbox"
148
+ aria-expanded={open}
149
+ class="sterling-select"
150
+ class:disabled
151
+ role="combobox"
152
+ tabindex="0"
153
+ on:click_outside={() => (open = false)}
154
+ on:click={onSelectClick}
155
+ on:blur
156
+ on:click
157
+ on:copy
158
+ on:cut
159
+ on:dblclick
160
+ on:focus
161
+ on:focusin
162
+ on:focusout
163
+ on:keydown={onSelectKeydown}
164
+ on:keydown
165
+ on:keypress
166
+ on:keyup
167
+ on:mousedown
168
+ on:mouseenter
169
+ on:mouseleave
170
+ on:mousemove
171
+ on:mouseover
172
+ on:mouseout
173
+ on:mouseup
174
+ on:wheel
175
+ on:paste
176
+ {...$$restProps}
177
+ >
178
+ <!-- svelte-ignore a11y-label-has-associated-control -->
179
+ <label class="sterling-select-label">
180
+ {#if $$slots.label}
181
+ <div class="label-content">
182
+ <slot name="label" />
183
+ </div>
184
+ {/if}
185
+ <div class="input">
186
+ <div class="value">
187
+ <slot name="value">
188
+ {items[selectedIndex]}
189
+ </slot>
190
+ </div>
191
+ <div class="button">
192
+ <slot name="button">
193
+ <div class="chevron" />
194
+ </slot>
195
+ </div>
196
+ </div>
197
+ </label>
198
+ <div
199
+ bind:this={popupRef}
200
+ class="popup"
201
+ class:open
202
+ id={popupId}
203
+ style="left:{popupPosition.x}px; top:{popupPosition.y}px"
204
+ >
205
+ <div class="popup-content">
206
+ <slot name="list">
207
+ {#if $$slots.default === true}
208
+ <List
209
+ bind:this={listRef}
210
+ selectedIndex={pendingSelectedIndex}
211
+ {items}
212
+ {disabled}
213
+ let:disabled
214
+ let:index
215
+ let:item
216
+ let:selected
217
+ on:click={onListClick}
218
+ on:keydown={onListKeydown}
219
+ on:itemSelected={onPendingItemSelected}
220
+ >
221
+ <slot {disabled} {index} {item} {selected} />
222
+ </List>
223
+ {:else}
224
+ <List
225
+ bind:this={listRef}
226
+ selectedIndex={pendingSelectedIndex}
227
+ {items}
228
+ {disabled}
229
+ let:disabled
230
+ let:index
231
+ let:item
232
+ let:selected
233
+ on:click={onListClick}
234
+ on:keydown={onListKeydown}
235
+ on:itemSelected={onPendingItemSelected}
236
+ />
237
+ {/if}
238
+ </slot>
239
+ </div>
240
+ </div>
241
+ </div>
242
+
243
+ <style>
244
+ .sterling-select {
245
+ background-color: var(--Input__background-color);
246
+ border-color: var(--Input__border-color);
247
+ border-radius: var(--Input__border-radius);
248
+ border-style: var(--Input__border-style);
249
+ border-width: var(--Input__border-width);
250
+ color: var(--Input__color);
251
+ display: flex;
252
+ flex-direction: row;
253
+ padding: 0;
254
+ position: relative;
255
+ transition: background-color 250ms, color 250ms, border-color 250ms;
256
+ }
257
+
258
+ .sterling-select:hover {
259
+ background-color: var(--Input__background-color--hover);
260
+ border-color: var(--Input__border-color--hover);
261
+ color: var(--Input__color--hover);
262
+ }
263
+
264
+ .sterling-select:focus-within {
265
+ background-color: var(--Input__background-color--focus);
266
+ border-color: var(--Input__border-color--focus);
267
+ color: var(--Input__color--focus);
268
+ outline-color: var(--Common__outline-color);
269
+ outline-offset: var(--Common__outline-offset);
270
+ outline-style: var(--Common__outline-style);
271
+ outline-width: var(--Common__outline-width);
272
+ }
273
+
274
+ .sterling-select.disabled {
275
+ background-color: var(--Input__background-color--disabled);
276
+ border-color: var(---Input__border-color--disabled);
277
+ color: var(--Input__color--disabled);
278
+ cursor: not-allowed;
279
+ outline: none;
280
+ }
281
+
282
+ .label {
283
+ display: flex;
284
+ flex-direction: column;
285
+ }
286
+
287
+ .label-content {
288
+ font-size: 0.7em;
289
+ margin: 0.5em 0.7em 0 0.7em;
290
+ color: var(--Display__color--subtle);
291
+ }
292
+
293
+ .input {
294
+ display: flex;
295
+ flex-direction: row;
296
+ align-items: stretch;
297
+ }
298
+
299
+ .value {
300
+ padding: 0.5em;
301
+ }
302
+
303
+ .chevron {
304
+ display: block;
305
+ position: relative;
306
+ border: none;
307
+ background: none;
308
+ margin: 0;
309
+ height: 100%;
310
+ width: 32px;
311
+ }
312
+
313
+ .chevron::after {
314
+ position: absolute;
315
+ content: '';
316
+ top: 50%;
317
+ left: 50%;
318
+ width: 7px;
319
+ height: 7px;
320
+ border-right: 3px solid currentColor;
321
+ border-top: 3px solid currentColor;
322
+ /*
323
+ The chevron is a right triangle, rotated to face down.
324
+ It should be moved up so it is centered vertically after rotation.
325
+ The amount to move is the hypotenuse of the right triangle of the chevron.
326
+ For a right triangle with equal a and b where c=1
327
+ a^2 + b^2 = c^2
328
+ a^2 + a^2 = c^2
329
+ 2a^2 = c^2
330
+ 2a^2 = 1
331
+ a^2 = 0.5
332
+ a = sqrt(0.5)
333
+ a = 0.707
334
+ */
335
+ transform: translate(-50%, calc(-50% / 0.707)) rotate(135deg);
336
+ transform-origin: 50% 50%;
337
+ }
338
+
339
+ .popup {
340
+ box-sizing: border-box;
341
+ display: none;
342
+ overflow: hidden;
343
+ position: absolute;
344
+ box-shadow: rgba(0, 0, 0, 0.2) 0px 2px 1px -1px, rgba(0, 0, 0, 0.14) 0px 1px 1px 0px,
345
+ rgba(0, 0, 0, 0.12) 0px 1px 3px 0px;
346
+ width: fit-content;
347
+ height: fit-content;
348
+ z-index: 1;
349
+ }
350
+
351
+ .popup.open {
352
+ display: grid;
353
+ grid-template-columns: 1fr;
354
+ grid-template-rows: 1fr;
355
+ }
356
+
357
+ .popup-content {
358
+ max-height: 15em;
359
+ }
360
+ </style>
@@ -0,0 +1,59 @@
1
+ import { SvelteComponentTyped } from "svelte";
2
+ declare const __propDef: {
3
+ props: {
4
+ [x: string]: any;
5
+ disabled?: boolean | undefined;
6
+ items?: any[] | undefined;
7
+ open?: boolean | undefined;
8
+ selectedIndex?: number | undefined;
9
+ selectedItem?: any;
10
+ };
11
+ events: {
12
+ blur: FocusEvent;
13
+ click: MouseEvent;
14
+ copy: ClipboardEvent;
15
+ cut: ClipboardEvent;
16
+ dblclick: MouseEvent;
17
+ focus: FocusEvent;
18
+ focusin: FocusEvent;
19
+ focusout: FocusEvent;
20
+ keydown: KeyboardEvent;
21
+ keypress: KeyboardEvent;
22
+ keyup: KeyboardEvent;
23
+ mousedown: MouseEvent;
24
+ mouseenter: MouseEvent;
25
+ mouseleave: MouseEvent;
26
+ mousemove: MouseEvent;
27
+ mouseover: MouseEvent;
28
+ mouseout: MouseEvent;
29
+ mouseup: MouseEvent;
30
+ wheel: WheelEvent;
31
+ paste: ClipboardEvent;
32
+ itemSelected: CustomEvent<any>;
33
+ itemSelectPending: CustomEvent<any>;
34
+ } & {
35
+ [evt: string]: CustomEvent<any>;
36
+ };
37
+ slots: {
38
+ label: {};
39
+ value: {};
40
+ button: {};
41
+ list: {};
42
+ default: {
43
+ disabled: boolean;
44
+ index: any;
45
+ item: any;
46
+ selected: any;
47
+ };
48
+ };
49
+ };
50
+ export type SelectProps = typeof __propDef.props;
51
+ export type SelectEvents = typeof __propDef.events;
52
+ export type SelectSlots = typeof __propDef.slots;
53
+ /**
54
+ * Pops up a list of items when clicked.
55
+ * A single item can be selected and is displayed as the value.
56
+ */
57
+ export default class Select extends SvelteComponentTyped<SelectProps, SelectEvents, SelectSlots> {
58
+ }
59
+ export {};
@@ -0,0 +1,280 @@
1
+ <script>import { round } from "lodash-es";
2
+ import { createEventDispatcher } from "svelte";
3
+ export let value = 0;
4
+ export let min = 0;
5
+ export let max = 100;
6
+ export let step = void 0;
7
+ export let precision = 0;
8
+ export let vertical = false;
9
+ export let disabled = false;
10
+ let sliderRef;
11
+ const dispatch = createEventDispatcher();
12
+ const raiseChange = (newValue) => {
13
+ dispatch("change", { value: newValue });
14
+ };
15
+ const getPrecision = (value2) => {
16
+ if (value2 !== void 0 && Number !== null && !Number.isNaN(value2)) {
17
+ const text = value2.toString();
18
+ const position = text.indexOf(".");
19
+ if (position !== -1) {
20
+ const fraction = text.substring(position + 1);
21
+ if (fraction) {
22
+ return fraction.length;
23
+ }
24
+ }
25
+ }
26
+ return 0;
27
+ };
28
+ $:
29
+ highestPrecision = Math.max(
30
+ precision,
31
+ getPrecision(min),
32
+ getPrecision(max),
33
+ getPrecision(step)
34
+ );
35
+ const setValue = (newValue) => {
36
+ value = round(Math.max(min, Math.min(max, newValue)), highestPrecision);
37
+ };
38
+ $: {
39
+ if (min > max) {
40
+ min = max;
41
+ }
42
+ }
43
+ $: {
44
+ if (value < min || value > max || value !== round(value, highestPrecision)) {
45
+ setValue(value);
46
+ }
47
+ }
48
+ $: {
49
+ if (step) {
50
+ let stepValue = Math.max(min, Math.min(value, max));
51
+ stepValue = Math.round(stepValue / step) * step + min;
52
+ if (stepValue !== value) {
53
+ setValue(stepValue);
54
+ }
55
+ }
56
+ }
57
+ $:
58
+ ratio = (value - min) / (max - min);
59
+ $:
60
+ changeBy = step ? step : 1;
61
+ const setValueByOffset = (offset) => {
62
+ if (sliderSize > 0) {
63
+ const positionRatio = Math.max(0, Math.min(1, offset / sliderSize));
64
+ const newValue = min + positionRatio * (max - min);
65
+ setValue(newValue);
66
+ }
67
+ };
68
+ $: {
69
+ raiseChange(value);
70
+ }
71
+ let sliderWidth;
72
+ let sliderHeight;
73
+ $:
74
+ sliderSize = vertical ? sliderHeight : sliderWidth;
75
+ $:
76
+ valueOffset = sliderSize * ratio;
77
+ const onPointerDown = (event) => {
78
+ if (!disabled) {
79
+ event.currentTarget.setPointerCapture(event.pointerId);
80
+ if (vertical) {
81
+ setValueByOffset(sliderRef.getBoundingClientRect().bottom - event.y);
82
+ } else {
83
+ setValueByOffset(event.x - sliderRef.getBoundingClientRect().left);
84
+ }
85
+ }
86
+ };
87
+ const onPointerMove = (event) => {
88
+ if (!disabled && event.currentTarget.hasPointerCapture(event.pointerId)) {
89
+ if (vertical) {
90
+ setValueByOffset(sliderRef.getBoundingClientRect().bottom - event.y);
91
+ } else {
92
+ setValueByOffset(event.x - sliderRef.getBoundingClientRect().left);
93
+ }
94
+ }
95
+ };
96
+ const onPointerUp = (event) => {
97
+ if (!disabled) {
98
+ event.currentTarget.releasePointerCapture(event.pointerId);
99
+ }
100
+ };
101
+ const onKeyDown = (event) => {
102
+ if (!disabled && !event.ctrlKey && !event.shiftKey && !event.altKey) {
103
+ switch (event.code) {
104
+ case "ArrowDown":
105
+ case "ArrowLeft":
106
+ setValue(value - changeBy);
107
+ event.preventDefault();
108
+ event.stopPropagation();
109
+ return;
110
+ case "ArrowRight":
111
+ case "ArrowUp":
112
+ setValue(value + changeBy);
113
+ event.preventDefault();
114
+ event.stopPropagation();
115
+ return;
116
+ }
117
+ }
118
+ };
119
+ </script>
120
+
121
+ <!-- @component
122
+ Slider lets the user chose a value within a min/max range by dragging a thumb button.
123
+ -->
124
+ <div
125
+ class="sterling-slider"
126
+ class:disabled
127
+ class:horizontal={!vertical}
128
+ class:vertical
129
+ tabindex={!disabled ? 0 : undefined}
130
+ {...$$restProps}
131
+ on:keydown={onKeyDown}
132
+ on:pointerdown={onPointerDown}
133
+ on:pointermove={onPointerMove}
134
+ on:pointerup={onPointerUp}
135
+ >
136
+ <div
137
+ class="container"
138
+ bind:this={sliderRef}
139
+ bind:clientWidth={sliderWidth}
140
+ bind:clientHeight={sliderHeight}
141
+ >
142
+ <div class="track" />
143
+ <div class="fill" style={vertical ? `height: ${valueOffset}px` : `width: ${valueOffset}px`} />
144
+ <div class="thumb" style={vertical ? `bottom: ${valueOffset}px` : `left: ${valueOffset}px`} />
145
+ </div>
146
+ </div>
147
+
148
+ <style>
149
+ .sterling-slider {
150
+ box-sizing: border-box;
151
+ outline: none;
152
+ padding: 0;
153
+ overflow: visible;
154
+ display: grid;
155
+ }
156
+
157
+ .sterling-slider.horizontal {
158
+ height: 2em;
159
+ }
160
+
161
+ .sterling-slider.vertical {
162
+ width: 2em;
163
+ }
164
+
165
+ .sterling-slider:focus-visible {
166
+ outline-color: var(--Common__outline-color);
167
+ outline-offset: var(--Common__outline-offset);
168
+ outline-style: var(--Common__outline-style);
169
+ outline-width: var(--Common__outline-width);
170
+ }
171
+
172
+ .container {
173
+ position: relative;
174
+ }
175
+
176
+ .sterling-slider.horizontal .container {
177
+ margin: 0 0.75em;
178
+ }
179
+
180
+ .sterling-slider.vertical .container {
181
+ margin: 0.75em 0;
182
+ }
183
+
184
+ .track {
185
+ position: absolute;
186
+ background: var(--Display__background-color);
187
+ }
188
+
189
+ .sterling-slider.horizontal .track {
190
+ left: 0;
191
+ right: 0;
192
+ top: 50%;
193
+ height: 3px;
194
+ transform: translate(0, -50%);
195
+ }
196
+
197
+ .sterling-slider.vertical .track {
198
+ bottom: 0;
199
+ left: 50%;
200
+ top: 0;
201
+ transform: translate(-50%, 0);
202
+ width: 3px;
203
+ }
204
+
205
+ .sterling-slider.disabled .track {
206
+ background: var(--Common__background-color--disabled);
207
+ }
208
+
209
+ .fill {
210
+ background: var(--Display__color);
211
+ position: absolute;
212
+ }
213
+
214
+ .sterling-slider.horizontal .fill {
215
+ height: 3px;
216
+ top: 50%;
217
+ transform: translate(0, -50%);
218
+ }
219
+
220
+ .sterling-slider.vertical .fill {
221
+ bottom: 0;
222
+ left: 50%;
223
+ transform: translate(-50%, 0);
224
+ width: 3px;
225
+ }
226
+
227
+ .sterling-slider.disabled .fill {
228
+ background: var(--Common__color--disabled);
229
+ }
230
+
231
+ .thumb {
232
+ background-color: var(--Button__background-color);
233
+ border-color: var(--Button__border-color);
234
+ border-radius: 10000px;
235
+ border-style: var(--Button__border-style);
236
+ border-width: var(--Button__border-width);
237
+ box-sizing: border-box;
238
+ color: var(--Button__color);
239
+ cursor: pointer;
240
+ display: block;
241
+ font: inherit;
242
+ height: 1.5em;
243
+ overflow: hidden;
244
+ padding: 0;
245
+ text-decoration: none;
246
+ transition: background-color 250ms, color 250ms, border-color 250ms;
247
+ white-space: nowrap;
248
+ position: absolute;
249
+ width: 1.5em;
250
+ }
251
+
252
+ .sterling-slider.horizontal .thumb {
253
+ top: 50%;
254
+ transform: translate(-50%, -50%);
255
+ }
256
+
257
+ .sterling-slider.vertical .thumb {
258
+ left: 50%;
259
+ transform: translate(-50%, 50%);
260
+ }
261
+
262
+ .thumb:hover {
263
+ background-color: var(--Button__background-color--hover);
264
+ border-color: var(--Button__border-color--hover);
265
+ color: var(--Button__color--hover);
266
+ }
267
+
268
+ .thumb:active {
269
+ background-color: var(--Button__background-color--active);
270
+ border-color: var(--Button__border-color--active);
271
+ color: var(--Button__color--active);
272
+ }
273
+
274
+ .sterling-slider.disabled .thumb {
275
+ background-color: var(--Common__background-color--disabled);
276
+ border-color: var(--Common__border-color--disabled);
277
+ color: var(--Common__color--disabled);
278
+ cursor: not-allowed;
279
+ }
280
+ </style>
@@ -0,0 +1,26 @@
1
+ import { SvelteComponentTyped } from "svelte";
2
+ declare const __propDef: {
3
+ props: {
4
+ [x: string]: any;
5
+ value?: number | undefined;
6
+ min?: number | undefined;
7
+ max?: number | undefined;
8
+ step?: number | undefined;
9
+ precision?: number | undefined;
10
+ vertical?: boolean | undefined;
11
+ disabled?: boolean | undefined;
12
+ };
13
+ events: {
14
+ change: CustomEvent<any>;
15
+ } & {
16
+ [evt: string]: CustomEvent<any>;
17
+ };
18
+ slots: {};
19
+ };
20
+ export type SliderProps = typeof __propDef.props;
21
+ export type SliderEvents = typeof __propDef.events;
22
+ export type SliderSlots = typeof __propDef.slots;
23
+ /** Slider lets the user chose a value within a min/max range by dragging a thumb button. */
24
+ export default class Slider extends SvelteComponentTyped<SliderProps, SliderEvents, SliderSlots> {
25
+ }
26
+ export {};