@mystravil/hardpoint 0.0.0-stage → 0.2.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 (126) hide show
  1. package/CHANGELOG.md +38 -0
  2. package/LICENSE +25 -0
  3. package/README.md +64 -3
  4. package/dist/base.css +625 -0
  5. package/dist/components/AppShell.svelte +698 -0
  6. package/dist/components/AppShell.svelte.d.ts +77 -0
  7. package/dist/components/Button.svelte +174 -0
  8. package/dist/components/Button.svelte.d.ts +20 -0
  9. package/dist/components/Checkbox.svelte +235 -0
  10. package/dist/components/Checkbox.svelte.d.ts +23 -0
  11. package/dist/components/CheckboxGroup.svelte +207 -0
  12. package/dist/components/CheckboxGroup.svelte.d.ts +44 -0
  13. package/dist/components/Combobox.svelte +901 -0
  14. package/dist/components/Combobox.svelte.d.ts +72 -0
  15. package/dist/components/Dialog.svelte +359 -0
  16. package/dist/components/Dialog.svelte.d.ts +43 -0
  17. package/dist/components/Disclosure.svelte +201 -0
  18. package/dist/components/Disclosure.svelte.d.ts +27 -0
  19. package/dist/components/EmptyState.svelte +88 -0
  20. package/dist/components/EmptyState.svelte.d.ts +22 -0
  21. package/dist/components/ErrorState.svelte +135 -0
  22. package/dist/components/ErrorState.svelte.d.ts +26 -0
  23. package/dist/components/ImageViewer.svelte +764 -0
  24. package/dist/components/ImageViewer.svelte.d.ts +55 -0
  25. package/dist/components/KeyValueList.svelte +114 -0
  26. package/dist/components/KeyValueList.svelte.d.ts +43 -0
  27. package/dist/components/ListDetail.svelte +218 -0
  28. package/dist/components/ListDetail.svelte.d.ts +37 -0
  29. package/dist/components/LoadRegion.svelte +219 -0
  30. package/dist/components/LoadRegion.svelte.d.ts +27 -0
  31. package/dist/components/MediaFrame.svelte +110 -0
  32. package/dist/components/MediaFrame.svelte.d.ts +20 -0
  33. package/dist/components/MediaImage.svelte +256 -0
  34. package/dist/components/MediaImage.svelte.d.ts +16 -0
  35. package/dist/components/Menu.svelte +258 -0
  36. package/dist/components/Menu.svelte.d.ts +36 -0
  37. package/dist/components/MenuCheckboxItem.svelte +119 -0
  38. package/dist/components/MenuCheckboxItem.svelte.d.ts +15 -0
  39. package/dist/components/MenuGroup.svelte +50 -0
  40. package/dist/components/MenuGroup.svelte.d.ts +15 -0
  41. package/dist/components/MenuItem.svelte +94 -0
  42. package/dist/components/MenuItem.svelte.d.ts +19 -0
  43. package/dist/components/MenuRadioGroup.svelte +62 -0
  44. package/dist/components/MenuRadioGroup.svelte.d.ts +20 -0
  45. package/dist/components/MenuRadioItem.svelte +110 -0
  46. package/dist/components/MenuRadioItem.svelte.d.ts +17 -0
  47. package/dist/components/MenuSeparator.svelte +28 -0
  48. package/dist/components/MenuSeparator.svelte.d.ts +6 -0
  49. package/dist/components/Meter.svelte +283 -0
  50. package/dist/components/Meter.svelte.d.ts +66 -0
  51. package/dist/components/NativeSelect.svelte +135 -0
  52. package/dist/components/NativeSelect.svelte.d.ts +52 -0
  53. package/dist/components/Notice.svelte +262 -0
  54. package/dist/components/Notice.svelte.d.ts +30 -0
  55. package/dist/components/Pagination.svelte +132 -0
  56. package/dist/components/Pagination.svelte.d.ts +18 -0
  57. package/dist/components/Popover.svelte +222 -0
  58. package/dist/components/Popover.svelte.d.ts +35 -0
  59. package/dist/components/Progress.svelte +157 -0
  60. package/dist/components/Progress.svelte.d.ts +34 -0
  61. package/dist/components/RadioGroup.svelte +440 -0
  62. package/dist/components/RadioGroup.svelte.d.ts +50 -0
  63. package/dist/components/Select.svelte +471 -0
  64. package/dist/components/Select.svelte.d.ts +48 -0
  65. package/dist/components/Skeleton.svelte +149 -0
  66. package/dist/components/Skeleton.svelte.d.ts +24 -0
  67. package/dist/components/Spinner.svelte +118 -0
  68. package/dist/components/Spinner.svelte.d.ts +13 -0
  69. package/dist/components/Stat.svelte +253 -0
  70. package/dist/components/Stat.svelte.d.ts +46 -0
  71. package/dist/components/Steps.svelte +189 -0
  72. package/dist/components/Steps.svelte.d.ts +31 -0
  73. package/dist/components/Switch.svelte +222 -0
  74. package/dist/components/Switch.svelte.d.ts +18 -0
  75. package/dist/components/Table.svelte +504 -0
  76. package/dist/components/Table.svelte.d.ts +92 -0
  77. package/dist/components/Tabs.svelte +246 -0
  78. package/dist/components/Tabs.svelte.d.ts +44 -0
  79. package/dist/components/Tag.svelte +40 -0
  80. package/dist/components/Tag.svelte.d.ts +15 -0
  81. package/dist/components/TextArea.svelte +272 -0
  82. package/dist/components/TextArea.svelte.d.ts +28 -0
  83. package/dist/components/TextField.svelte +256 -0
  84. package/dist/components/TextField.svelte.d.ts +32 -0
  85. package/dist/components/Timeline.svelte +156 -0
  86. package/dist/components/Timeline.svelte.d.ts +30 -0
  87. package/dist/components/Toaster.svelte +411 -0
  88. package/dist/components/Toaster.svelte.d.ts +11 -0
  89. package/dist/components/Tooltip.svelte +200 -0
  90. package/dist/components/Tooltip.svelte.d.ts +38 -0
  91. package/dist/components/TooltipGroup.svelte +46 -0
  92. package/dist/components/TooltipGroup.svelte.d.ts +29 -0
  93. package/dist/components/internal/Scrim.svelte +52 -0
  94. package/dist/components/internal/Scrim.svelte.d.ts +15 -0
  95. package/dist/components/internal/Sheet.svelte +263 -0
  96. package/dist/components/internal/Sheet.svelte.d.ts +36 -0
  97. package/dist/components/internal/fade-in.d.ts +10 -0
  98. package/dist/components/internal/fade-in.js +20 -0
  99. package/dist/components/internal/groups.d.ts +14 -0
  100. package/dist/components/internal/groups.js +23 -0
  101. package/dist/components/internal/resolve.d.ts +8 -0
  102. package/dist/components/internal/resolve.js +247 -0
  103. package/dist/components/internal/swipe-dismiss.d.ts +47 -0
  104. package/dist/components/internal/swipe-dismiss.js +220 -0
  105. package/dist/components/internal/visual-viewport.svelte.d.ts +35 -0
  106. package/dist/components/internal/visual-viewport.svelte.js +79 -0
  107. package/dist/components/toast.svelte.d.ts +57 -0
  108. package/dist/components/toast.svelte.js +84 -0
  109. package/dist/fonts/OFL-geist-pixel.txt +92 -0
  110. package/dist/fonts/OFL.txt +93 -0
  111. package/dist/fonts/geist-latin-variable.woff2 +0 -0
  112. package/dist/fonts/geist-mono-latin-variable.woff2 +0 -0
  113. package/dist/fonts/geist-pixel-square.woff2 +0 -0
  114. package/dist/fonts.css +71 -0
  115. package/dist/index.d.ts +45 -0
  116. package/dist/index.js +45 -0
  117. package/dist/motion/Motion.svelte +15 -0
  118. package/dist/motion/Motion.svelte.d.ts +9 -0
  119. package/dist/motion/context.d.ts +18 -0
  120. package/dist/motion/context.js +26 -0
  121. package/dist/motion/index.d.ts +3 -0
  122. package/dist/motion/index.js +3 -0
  123. package/dist/motion/transitions.d.ts +31 -0
  124. package/dist/motion/transitions.js +112 -0
  125. package/dist/tokens.css +283 -0
  126. package/package.json +76 -6
@@ -0,0 +1,283 @@
1
+ <script lang="ts" module>
2
+ import { Meter as BitsMeter } from 'bits-ui';
3
+
4
+ /** A status hue for the bar: `ok` fine, `warn` needs attention, `danger` bad. */
5
+ export type MeterTone = 'ok' | 'warn' | 'danger';
6
+
7
+ export type MeterProps = Omit<BitsMeter.RootProps, 'value' | 'child' | 'children'> & {
8
+ /**
9
+ * The measurement, from `min` to `max`. Pass the real figure each time
10
+ * it changes: the bar never moves on its own. A value outside the
11
+ * range is held at its ends for the bar and `aria-valuenow`. Work that
12
+ * is getting done shows a `Progress` instead.
13
+ */
14
+ value: number;
15
+ /** The top of the range. Default 100. */
16
+ max?: number;
17
+ /** The bottom of the range. Default 0. */
18
+ min?: number;
19
+ /**
20
+ * How many segments the bar has (default 20, at least 2). A segment
21
+ * lights once the value has passed its whole share, so the bar never
22
+ * shows more than the value.
23
+ */
24
+ segments?: number;
25
+ /**
26
+ * A line above the bar saying what is measured, as written (never
27
+ * caps: it may hold a name). It names the meter; without it, pass
28
+ * `aria-label` or `aria-labelledby`.
29
+ */
30
+ label?: string;
31
+ /**
32
+ * The value in words, such as `4.2 of 5 GB`. Shown at the end of the
33
+ * label line and read as `aria-valuetext`. Without it both give the
34
+ * share of the range, rounded down.
35
+ */
36
+ valueText?: string;
37
+ /**
38
+ * The value at and above which the meter shows `warn`. Default 80% of
39
+ * the range. `null` turns the warn step off.
40
+ */
41
+ warnAt?: number | null;
42
+ /**
43
+ * The value at and above which the meter shows `danger`. Default
44
+ * `max`. `null` turns the danger step off.
45
+ */
46
+ dangerAt?: number | null;
47
+ /**
48
+ * Replaces the thresholds with a colour the app chooses. Use it where
49
+ * lower is worse, or for a state the value cannot show. Colour never
50
+ * carries meaning alone: say the reason in `valueText` or `detail`.
51
+ */
52
+ tone?: MeterTone;
53
+ /**
54
+ * A reference value drawn as a thin mark across the bar: a target, an
55
+ * expected level, an earlier reading. Held inside the range. The mark
56
+ * is visual only and hidden from assistive technology, so say what it
57
+ * stands for in `detail`.
58
+ */
59
+ marker?: number;
60
+ /**
61
+ * A muted line under the bar, as written. Assistive technology reads
62
+ * it as the meter's description; the app's own `aria-describedby`
63
+ * ids follow it.
64
+ */
65
+ detail?: string;
66
+ };
67
+ </script>
68
+
69
+ <script lang="ts">
70
+ let {
71
+ value,
72
+ max = 100,
73
+ min = 0,
74
+ segments = 20,
75
+ label,
76
+ valueText,
77
+ warnAt,
78
+ dangerAt,
79
+ tone,
80
+ marker,
81
+ detail,
82
+ id,
83
+ class: className,
84
+ ref = $bindable(null),
85
+ 'aria-describedby': describedby,
86
+ ...rest
87
+ }: MeterProps = $props();
88
+
89
+ const uid = $props.id();
90
+ const labelId = `${uid}-label`;
91
+ const detailId = `${uid}-detail`;
92
+
93
+ // The value inside its range, so the bar and aria-valuenow agree. An
94
+ // empty range counts as full.
95
+ const current = $derived(Math.min(max, Math.max(min, value)));
96
+ const span = $derived(max - min);
97
+ const count = $derived(Math.max(2, Math.floor(segments)));
98
+ // Round down: a segment lights only when its whole share is reached.
99
+ // Multiply before dividing and clear float noise before rounding down,
100
+ // so 58 of 100 and 0.58 of 1 are both 58% and not 57.99…
101
+ const share = (n: number) => Math.floor(Number((((current - min) * n) / span).toPrecision(12)));
102
+ const lit = $derived(current >= max || span <= 0 ? count : share(count));
103
+ const percent = $derived(span > 0 ? share(100) : 100);
104
+ const shown = $derived(valueText ?? `${percent}%`);
105
+
106
+ // Thresholds apply to the value inside the range. `undefined` takes the
107
+ // default; `null` turns the step off. 0.8 of the span is cleared of float
108
+ // noise so a value on the line counts.
109
+ const warnLine = $derived(
110
+ warnAt === undefined ? min + Number((0.8 * span).toPrecision(12)) : warnAt
111
+ );
112
+ const dangerLine = $derived(dangerAt === undefined ? max : dangerAt);
113
+ const shownTone = $derived<MeterTone | undefined>(
114
+ tone ??
115
+ (dangerLine !== null && current >= dangerLine
116
+ ? 'danger'
117
+ : warnLine !== null && current >= warnLine
118
+ ? 'warn'
119
+ : undefined)
120
+ );
121
+
122
+ // The marker's place across the bar, held inside the range.
123
+ const markerAt = $derived(
124
+ marker === undefined
125
+ ? undefined
126
+ : span > 0
127
+ ? Math.min(1, Math.max(0, (marker - min) / span)) * 100
128
+ : 0
129
+ );
130
+
131
+ const describedBy = $derived([detail ? detailId : undefined, describedby].filter(Boolean).join(' ') || undefined);
132
+ </script>
133
+
134
+ <!--
135
+ Bits UI Meter, rendered through its `child` snippet so the element is this
136
+ component's markup and its styles stay scoped. The root is the meter
137
+ (`role`, `aria-valuemin`, `aria-valuemax`, `aria-valuenow`); `id`, extra
138
+ attributes and `class` go to it. Its children are presentational to
139
+ assistive technology, so the label line names it through
140
+ `aria-labelledby`, the value as shown reaches it as `aria-valuetext`, and
141
+ the detail line describes it. The marker is a decoration.
142
+ A segment that lights, goes out or changes tone fades its colour over
143
+ --dur-segment; the marker moves at once.
144
+ -->
145
+ <BitsMeter.Root
146
+ bind:ref
147
+ value={current}
148
+ {max}
149
+ {min}
150
+ aria-labelledby={label ? labelId : undefined}
151
+ aria-valuetext={shown}
152
+ aria-describedby={describedBy}
153
+ {...rest}
154
+ >
155
+ {#snippet child({ props })}
156
+ <div
157
+ {...props}
158
+ {id}
159
+ class={['hp-meter', className]}
160
+ data-tone={shownTone}
161
+ data-lit={lit}
162
+ data-segments={count}
163
+ >
164
+ {#if label}
165
+ <div class="hp-meter-line">
166
+ <span id={labelId} class="hp-meter-label">{label}</span>
167
+ <span class="hp-meter-value">{shown}</span>
168
+ </div>
169
+ {/if}
170
+ <div class="hp-meter-bar" style:--segments={count}>
171
+ {#each { length: count }, i}
172
+ <span class="hp-meter-segment" data-lit={i < lit || undefined}></span>
173
+ {/each}
174
+ {#if markerAt !== undefined}
175
+ <span class="hp-meter-marker" aria-hidden="true" style:left="{markerAt}%"></span>
176
+ {/if}
177
+ </div>
178
+ {#if detail}
179
+ <p id={detailId} class="hp-meter-detail">{detail}</p>
180
+ {/if}
181
+ </div>
182
+ {/snippet}
183
+ </BitsMeter.Root>
184
+
185
+ <style>
186
+ /* The bar fills the width it is given; the app sets a narrower one. */
187
+ .hp-meter {
188
+ display: grid;
189
+ gap: var(--space-2);
190
+ min-width: 0;
191
+ }
192
+
193
+ /* 16px on touch, the dense 14px on fine pointers. The value keeps the
194
+ end of the line; a long label wraps rather than widen the page. */
195
+ .hp-meter-line {
196
+ display: flex;
197
+ align-items: baseline;
198
+ justify-content: space-between;
199
+ gap: var(--space-4);
200
+ font-size: var(--type-body);
201
+ line-height: 24px;
202
+ }
203
+ @media (pointer: fine) {
204
+ .hp-meter-line {
205
+ font-size: var(--type-dense);
206
+ line-height: 20px;
207
+ }
208
+ }
209
+ .hp-meter-label {
210
+ min-width: 0;
211
+ color: var(--text);
212
+ overflow-wrap: anywhere;
213
+ }
214
+ /* Muted with no tone; the tone's hue otherwise, so the words and the
215
+ bar agree. The value text is always there, so colour never carries the
216
+ meaning alone. */
217
+ .hp-meter-value {
218
+ flex: none;
219
+ color: var(--muted);
220
+ font-variant-numeric: tabular-nums;
221
+ }
222
+ .hp-meter[data-tone='ok'] .hp-meter-value {
223
+ color: var(--ok);
224
+ }
225
+ .hp-meter[data-tone='warn'] .hp-meter-value {
226
+ color: var(--warn);
227
+ }
228
+ .hp-meter[data-tone='danger'] .hp-meter-value {
229
+ color: var(--danger);
230
+ }
231
+
232
+ /* Separate segments as in Progress (BRIEF, visual rule 7): lit in the
233
+ tone's colour, the 3:1 control boundary while unlit. Square, 4px
234
+ apart, one row. */
235
+ .hp-meter-bar {
236
+ position: relative;
237
+ display: grid;
238
+ grid-template-columns: repeat(var(--segments), minmax(0, 1fr));
239
+ gap: var(--space-1);
240
+ height: 12px;
241
+ }
242
+ /* A segment that lights, goes out or changes tone fades its colour over
243
+ --dur-segment (BRIEF, short motion). A CSS transition never runs on an
244
+ element's first style, so a meter shows its value at once when it
245
+ renders. 0ms is a snap. Colour only, so reduced motion keeps it. */
246
+ .hp-meter-segment {
247
+ background: var(--border-control);
248
+ transition: background-color var(--dur-segment) var(--settle);
249
+ }
250
+ .hp-meter-segment[data-lit] {
251
+ background: var(--accent);
252
+ }
253
+ .hp-meter[data-tone='ok'] .hp-meter-segment[data-lit] {
254
+ background: var(--ok);
255
+ }
256
+ .hp-meter[data-tone='warn'] .hp-meter-segment[data-lit] {
257
+ background: var(--warn);
258
+ }
259
+ .hp-meter[data-tone='danger'] .hp-meter-segment[data-lit] {
260
+ background: var(--danger);
261
+ }
262
+
263
+ /* A 2px line in the text colour, 4px past the bar at each end. A 1px
264
+ canvas edge on each side lets it read over lit segments of every tone,
265
+ unlit ones and the gaps, in both themes. It moves at once. */
266
+ .hp-meter-marker {
267
+ position: absolute;
268
+ top: calc(var(--space-1) * -1);
269
+ bottom: calc(var(--space-1) * -1);
270
+ width: 2px;
271
+ margin-left: -1px;
272
+ background: var(--text);
273
+ box-shadow: 0 0 0 1px var(--canvas);
274
+ pointer-events: none;
275
+ }
276
+
277
+ .hp-meter-detail {
278
+ margin: 0;
279
+ color: var(--muted);
280
+ font-size: var(--type-caption);
281
+ line-height: 18px;
282
+ }
283
+ </style>
@@ -0,0 +1,66 @@
1
+ import { Meter as BitsMeter } from 'bits-ui';
2
+ /** A status hue for the bar: `ok` fine, `warn` needs attention, `danger` bad. */
3
+ export type MeterTone = 'ok' | 'warn' | 'danger';
4
+ export type MeterProps = Omit<BitsMeter.RootProps, 'value' | 'child' | 'children'> & {
5
+ /**
6
+ * The measurement, from `min` to `max`. Pass the real figure each time
7
+ * it changes: the bar never moves on its own. A value outside the
8
+ * range is held at its ends for the bar and `aria-valuenow`. Work that
9
+ * is getting done shows a `Progress` instead.
10
+ */
11
+ value: number;
12
+ /** The top of the range. Default 100. */
13
+ max?: number;
14
+ /** The bottom of the range. Default 0. */
15
+ min?: number;
16
+ /**
17
+ * How many segments the bar has (default 20, at least 2). A segment
18
+ * lights once the value has passed its whole share, so the bar never
19
+ * shows more than the value.
20
+ */
21
+ segments?: number;
22
+ /**
23
+ * A line above the bar saying what is measured, as written (never
24
+ * caps: it may hold a name). It names the meter; without it, pass
25
+ * `aria-label` or `aria-labelledby`.
26
+ */
27
+ label?: string;
28
+ /**
29
+ * The value in words, such as `4.2 of 5 GB`. Shown at the end of the
30
+ * label line and read as `aria-valuetext`. Without it both give the
31
+ * share of the range, rounded down.
32
+ */
33
+ valueText?: string;
34
+ /**
35
+ * The value at and above which the meter shows `warn`. Default 80% of
36
+ * the range. `null` turns the warn step off.
37
+ */
38
+ warnAt?: number | null;
39
+ /**
40
+ * The value at and above which the meter shows `danger`. Default
41
+ * `max`. `null` turns the danger step off.
42
+ */
43
+ dangerAt?: number | null;
44
+ /**
45
+ * Replaces the thresholds with a colour the app chooses. Use it where
46
+ * lower is worse, or for a state the value cannot show. Colour never
47
+ * carries meaning alone: say the reason in `valueText` or `detail`.
48
+ */
49
+ tone?: MeterTone;
50
+ /**
51
+ * A reference value drawn as a thin mark across the bar: a target, an
52
+ * expected level, an earlier reading. Held inside the range. The mark
53
+ * is visual only and hidden from assistive technology, so say what it
54
+ * stands for in `detail`.
55
+ */
56
+ marker?: number;
57
+ /**
58
+ * A muted line under the bar, as written. Assistive technology reads
59
+ * it as the meter's description; the app's own `aria-describedby`
60
+ * ids follow it.
61
+ */
62
+ detail?: string;
63
+ };
64
+ declare const Meter: import("svelte").Component<MeterProps, {}, "ref">;
65
+ type Meter = ReturnType<typeof Meter>;
66
+ export default Meter;
@@ -0,0 +1,135 @@
1
+ <script lang="ts" module>
2
+ import type { HTMLSelectAttributes } from 'svelte/elements';
3
+
4
+ export type NativeSelectOption<V = string> = {
5
+ value: V;
6
+ label: string;
7
+ /**
8
+ * The `<optgroup>` the option sits in. Options with the same group share
9
+ * one, in the order each group first appears; options without one come
10
+ * first.
11
+ */
12
+ group?: string;
13
+ disabled?: boolean;
14
+ };
15
+
16
+ /** From which width the label sits beside the well: 600px, 900px or 1200px. */
17
+ export type NativeSelectBreakpoint = 'medium' | 'expanded' | 'large';
18
+ </script>
19
+
20
+ <script lang="ts" generics="V = string">
21
+ import type { Snippet } from 'svelte';
22
+ import { groupOptions } from './internal/groups.js';
23
+
24
+ const uid = $props.id();
25
+
26
+ let {
27
+ label,
28
+ value = $bindable(),
29
+ options,
30
+ labelBeside = false,
31
+ id = uid,
32
+ class: className,
33
+ children,
34
+ ...rest
35
+ }: Omit<HTMLSelectAttributes, 'value' | 'multiple' | 'children'> & {
36
+ /** The caps field label. */
37
+ label: string;
38
+ /** The chosen option's value. Bindable. */
39
+ value?: V;
40
+ /** The choices. Without it, pass `<option>` elements as children. */
41
+ options?: NativeSelectOption<V>[];
42
+ /**
43
+ * The label sits above the well by default. `true` puts it beside the
44
+ * well at every width; a breakpoint puts it beside from that width up.
45
+ */
46
+ labelBeside?: boolean | NativeSelectBreakpoint;
47
+ children?: Snippet;
48
+ } = $props();
49
+
50
+ const grouped = $derived(options && groupOptions(options));
51
+ </script>
52
+
53
+ <!--
54
+ A single choice from a short fixed list. A native <select>, so phones keep
55
+ their own picker (BRIEF, Apps and phones). Extra attributes, handlers and
56
+ `class` go to the <select>.
57
+ -->
58
+ <div
59
+ class="hp-native-select"
60
+ data-label-beside={labelBeside === true ? 'always' : labelBeside || undefined}
61
+ >
62
+ <label class="hp-label" for={id}>{label}</label>
63
+ <span class="hp-well-chevron">
64
+ <select {...rest} {id} class={['hp-well', className]} bind:value>
65
+ {#if grouped}
66
+ {#each grouped.loose as option (option.value)}
67
+ <option value={option.value} disabled={option.disabled}>{option.label}</option>
68
+ {/each}
69
+ {#each grouped.groups as group (group.name)}
70
+ <optgroup label={group.name}>
71
+ {#each group.items as option (option.value)}
72
+ <option value={option.value} disabled={option.disabled}>{option.label}</option>
73
+ {/each}
74
+ </optgroup>
75
+ {/each}
76
+ {:else}
77
+ {@render children?.()}
78
+ {/if}
79
+ </select>
80
+ </span>
81
+ </div>
82
+
83
+ <style>
84
+ .hp-native-select {
85
+ display: grid;
86
+ gap: var(--space-1);
87
+ min-width: 0;
88
+ }
89
+ label {
90
+ color: var(--muted);
91
+ }
92
+ /* The well fills its column, never narrower than a short choice and the
93
+ chevron. The wrapper is the grid or flex item, so it holds the minimum;
94
+ a select's own minimum would be its longest option. */
95
+ .hp-well-chevron {
96
+ width: 100%;
97
+ min-width: 136px;
98
+ }
99
+ select {
100
+ display: block;
101
+ width: 100%;
102
+ }
103
+
104
+ /* Beside the well, the label keeps its one line and the well gives way. */
105
+ [data-label-beside] label {
106
+ flex: none;
107
+ }
108
+
109
+ [data-label-beside='always'] {
110
+ display: flex;
111
+ align-items: center;
112
+ gap: var(--space-2);
113
+ }
114
+ @media (min-width: 600px) {
115
+ [data-label-beside='medium'] {
116
+ display: flex;
117
+ align-items: center;
118
+ gap: var(--space-2);
119
+ }
120
+ }
121
+ @media (min-width: 900px) {
122
+ [data-label-beside='expanded'] {
123
+ display: flex;
124
+ align-items: center;
125
+ gap: var(--space-2);
126
+ }
127
+ }
128
+ @media (min-width: 1200px) {
129
+ [data-label-beside='large'] {
130
+ display: flex;
131
+ align-items: center;
132
+ gap: var(--space-2);
133
+ }
134
+ }
135
+ </style>
@@ -0,0 +1,52 @@
1
+ import type { HTMLSelectAttributes } from 'svelte/elements';
2
+ export type NativeSelectOption<V = string> = {
3
+ value: V;
4
+ label: string;
5
+ /**
6
+ * The `<optgroup>` the option sits in. Options with the same group share
7
+ * one, in the order each group first appears; options without one come
8
+ * first.
9
+ */
10
+ group?: string;
11
+ disabled?: boolean;
12
+ };
13
+ /** From which width the label sits beside the well: 600px, 900px or 1200px. */
14
+ export type NativeSelectBreakpoint = 'medium' | 'expanded' | 'large';
15
+ import type { Snippet } from 'svelte';
16
+ declare function $$render<V = string>(): {
17
+ props: Omit<HTMLSelectAttributes, "children" | "value" | "multiple"> & {
18
+ /** The caps field label. */
19
+ label: string;
20
+ /** The chosen option's value. Bindable. */
21
+ value?: V;
22
+ /** The choices. Without it, pass `<option>` elements as children. */
23
+ options?: NativeSelectOption<V>[];
24
+ /**
25
+ * The label sits above the well by default. `true` puts it beside the
26
+ * well at every width; a breakpoint puts it beside from that width up.
27
+ */
28
+ labelBeside?: boolean | NativeSelectBreakpoint;
29
+ children?: Snippet;
30
+ };
31
+ exports: {};
32
+ bindings: "value";
33
+ slots: {};
34
+ events: {};
35
+ };
36
+ declare class __sveltets_Render<V = string> {
37
+ props(): ReturnType<typeof $$render<V>>['props'];
38
+ events(): ReturnType<typeof $$render<V>>['events'];
39
+ slots(): ReturnType<typeof $$render<V>>['slots'];
40
+ bindings(): "value";
41
+ exports(): {};
42
+ }
43
+ interface $$IsomorphicComponent {
44
+ new <V = string>(options: import('svelte').ComponentConstructorOptions<ReturnType<__sveltets_Render<V>['props']>>): import('svelte').SvelteComponent<ReturnType<__sveltets_Render<V>['props']>, ReturnType<__sveltets_Render<V>['events']>, ReturnType<__sveltets_Render<V>['slots']>> & {
45
+ $$bindings?: ReturnType<__sveltets_Render<V>['bindings']>;
46
+ } & ReturnType<__sveltets_Render<V>['exports']>;
47
+ <V = string>(internal: unknown, props: ReturnType<__sveltets_Render<V>['props']> & {}): ReturnType<__sveltets_Render<V>['exports']>;
48
+ z_$$bindings?: ReturnType<__sveltets_Render<any>['bindings']>;
49
+ }
50
+ declare const NativeSelect: $$IsomorphicComponent;
51
+ type NativeSelect<V = string> = InstanceType<typeof NativeSelect<V>>;
52
+ export default NativeSelect;