@mystravil/hardpoint 0.1.0 → 0.2.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.
Files changed (67) hide show
  1. package/CHANGELOG.md +42 -18
  2. package/LICENSE +25 -25
  3. package/README.md +67 -64
  4. package/dist/base.css +625 -346
  5. package/dist/components/AppShell.svelte +698 -698
  6. package/dist/components/Button.svelte +174 -174
  7. package/dist/components/Checkbox.svelte +235 -198
  8. package/dist/components/Checkbox.svelte.d.ts +9 -2
  9. package/dist/components/CheckboxGroup.svelte +207 -99
  10. package/dist/components/CheckboxGroup.svelte.d.ts +23 -4
  11. package/dist/components/Combobox.svelte +901 -824
  12. package/dist/components/Combobox.svelte.d.ts +10 -1
  13. package/dist/components/Dialog.svelte +359 -356
  14. package/dist/components/Disclosure.svelte +201 -201
  15. package/dist/components/EmptyState.svelte +88 -88
  16. package/dist/components/ErrorState.svelte +135 -135
  17. package/dist/components/ImageViewer.svelte +764 -0
  18. package/dist/components/ImageViewer.svelte.d.ts +55 -0
  19. package/dist/components/KeyValueList.svelte +114 -114
  20. package/dist/components/ListDetail.svelte +218 -0
  21. package/dist/components/ListDetail.svelte.d.ts +37 -0
  22. package/dist/components/LoadRegion.svelte +219 -219
  23. package/dist/components/MediaFrame.svelte +110 -110
  24. package/dist/components/MediaImage.svelte +256 -256
  25. package/dist/components/Menu.svelte +258 -258
  26. package/dist/components/MenuCheckboxItem.svelte +119 -119
  27. package/dist/components/MenuGroup.svelte +50 -50
  28. package/dist/components/MenuItem.svelte +94 -94
  29. package/dist/components/MenuRadioGroup.svelte +62 -62
  30. package/dist/components/MenuRadioItem.svelte +110 -110
  31. package/dist/components/MenuSeparator.svelte +28 -28
  32. package/dist/components/Meter.svelte +283 -0
  33. package/dist/components/Meter.svelte.d.ts +66 -0
  34. package/dist/components/NativeSelect.svelte +18 -2
  35. package/dist/components/NativeSelect.svelte.d.ts +6 -0
  36. package/dist/components/Notice.svelte +262 -0
  37. package/dist/components/Notice.svelte.d.ts +30 -0
  38. package/dist/components/Popover.svelte +222 -222
  39. package/dist/components/Progress.svelte +157 -157
  40. package/dist/components/RadioGroup.svelte +440 -0
  41. package/dist/components/RadioGroup.svelte.d.ts +50 -0
  42. package/dist/components/Select.svelte +471 -405
  43. package/dist/components/Select.svelte.d.ts +6 -0
  44. package/dist/components/Skeleton.svelte +149 -149
  45. package/dist/components/Spinner.svelte +118 -117
  46. package/dist/components/Stat.svelte +253 -253
  47. package/dist/components/Steps.svelte +189 -189
  48. package/dist/components/Switch.svelte +222 -222
  49. package/dist/components/Table.svelte +504 -504
  50. package/dist/components/Tabs.svelte +246 -246
  51. package/dist/components/Tag.svelte +40 -40
  52. package/dist/components/TextArea.svelte +272 -0
  53. package/dist/components/TextArea.svelte.d.ts +28 -0
  54. package/dist/components/TextField.svelte +256 -256
  55. package/dist/components/Timeline.svelte +156 -156
  56. package/dist/components/Toaster.svelte +411 -411
  57. package/dist/components/Tooltip.svelte +200 -200
  58. package/dist/components/TooltipGroup.svelte +46 -46
  59. package/dist/components/internal/Scrim.svelte +52 -52
  60. package/dist/components/internal/Sheet.svelte +263 -263
  61. package/dist/components/internal/groups.d.ts +14 -0
  62. package/dist/components/internal/groups.js +23 -0
  63. package/dist/fonts.css +71 -71
  64. package/dist/index.d.ts +6 -0
  65. package/dist/index.js +6 -0
  66. package/dist/tokens.css +283 -279
  67. package/package.json +81 -76
@@ -1,253 +1,253 @@
1
- <script lang="ts" module>
2
- import type { HTMLAttributes } from 'svelte/elements';
3
-
4
- /**
5
- * `standard` (default): the size for a row of several stats. `display`:
6
- * the 32 to 56px display size, for the view's one display subject.
7
- */
8
- export type StatVariant = 'standard' | 'display';
9
-
10
- /** A status hue for the delta: `ok` good, `warn` needs attention, `danger` bad. */
11
- export type StatTone = 'ok' | 'warn' | 'danger';
12
-
13
- export type StatProps = Omit<HTMLAttributes<HTMLDListElement>, 'children'> & {
14
- /** The caps label. A fixed string the app defines. */
15
- label: string;
16
- /**
17
- * The value in Geist with tabular figures. A string is shown as given
18
- * and changes at once. A number is shown through `format`; when it
19
- * changes after the first render, it ticks from the shown figure to
20
- * the new one (BRIEF, motion rule 5).
21
- */
22
- value: string | number;
23
- /**
24
- * Formats a number `value` at rest and at every step of a tick, for
25
- * example `(n) => n.toLocaleString('en-US')`. Each step is rounded to
26
- * the larger number of decimal places of the old and new value before
27
- * it is formatted. Without `format` a number shows as `String(value)`,
28
- * and the steps show those decimal places.
29
- */
30
- format?: (value: number) => string;
31
- /** A unit after the value, smaller and in the muted ink: `EUR`, `ms`, `tasks`. */
32
- unit?: string;
33
- /**
34
- * The change, in the muted ink. A number gets its sign: `+12`, `−3`
35
- * (a true minus), `0`. A string is shown as written, sign included.
36
- */
37
- delta?: string | number;
38
- /** What the delta compares with, after it: `since last week`. */
39
- deltaNote?: string;
40
- /**
41
- * Colours the delta in a status hue. Without it the delta stays muted.
42
- * The label or `deltaNote` must say the same, because colour is never
43
- * the only signal.
44
- */
45
- tone?: StatTone;
46
- variant?: StatVariant;
47
- };
48
- </script>
49
-
50
- <script lang="ts">
51
- import { untrack } from 'svelte';
52
- import { Tween } from 'svelte/motion';
53
- import { curve, duration, useMotion } from '../motion/index.js';
54
-
55
- let {
56
- label,
57
- value,
58
- format,
59
- unit,
60
- delta,
61
- deltaNote,
62
- tone,
63
- variant = 'standard',
64
- class: className,
65
- ...rest
66
- }: StatProps = $props();
67
-
68
- const change = $derived(
69
- typeof delta === 'number'
70
- ? delta > 0
71
- ? `+${delta}`
72
- : delta < 0
73
- ? `\u2212${-delta}`
74
- : String(delta)
75
- : delta
76
- );
77
-
78
- // ---- Tick (BRIEF, motion rule 5) ---------------------------------------
79
-
80
- // A number value that changes after the first render ticks from the
81
- // figure on screen to the new one. Quiet motion, so it runs in both tiers.
82
- // Strings, reduced motion and live regions change at once.
83
- const LIVE = '[aria-live]:not([aria-live="off"]), [role="status"], [role="log"], [role="alert"]';
84
-
85
- const motion = useMotion();
86
- let valueEl: HTMLElement | undefined;
87
- // Starts on the first value, so nothing moves on the first render.
88
- const tween = new Tween(untrack(() => (typeof value === 'number' ? value : 0)));
89
- let last = untrack(() => value);
90
- // The decimal places of the running tick's steps.
91
- let places = $state(0);
92
- // The figure on screen when the tick began. A hidden copy keeps its width.
93
- let from = $state('');
94
-
95
- /** The decimal places of `n` as `String()` writes it, exponent included. */
96
- function decimals(n: number): number {
97
- const [digits, exp = '0'] = String(n).split('e');
98
- return Math.min(100, Math.max(0, (digits.split('.')[1]?.length ?? 0) - Number(exp)));
99
- }
100
- const atRest = (n: number) => (format ? format(n) : String(n));
101
- /** One step: rounded to `p` places, never `-0`, then formatted. */
102
- function atStep(n: number, p: number): string {
103
- const r = Number(n.toFixed(p)) || 0;
104
- return format ? format(r) : r.toFixed(p);
105
- }
106
-
107
- const text = $derived(typeof value === 'number' ? atRest(value) : value);
108
- const ticking = $derived(
109
- typeof value === 'number' && !Number.isNaN(value) && tween.current !== value
110
- );
111
- const step = $derived(ticking ? atStep(tween.current, places) : '');
112
-
113
- // Before the DOM updates, so the first frame after a change still shows
114
- // the old figure. Duration and curve are read from the value element at
115
- // the change, so a retuned token applies. A change during a tick starts
116
- // from the step on screen.
117
- $effect.pre(() => {
118
- const to = value;
119
- untrack(() => {
120
- const was = last;
121
- last = to;
122
- if (typeof to !== 'number' || to === was) return;
123
- const resting = tween.current === tween.target;
124
- const shown = resting ? tween.current : Number(tween.current.toFixed(places)) || 0;
125
- const ms =
126
- valueEl && typeof was === 'number' && !motion.reduced && !valueEl.closest(LIVE)
127
- ? duration(valueEl, '--dur-tick')
128
- : 0;
129
- if (!valueEl || ms <= 0 || !Number.isFinite(shown) || !Number.isFinite(to)) {
130
- void tween.set(to, { duration: 0 });
131
- return;
132
- }
133
- from = resting ? atRest(shown) : atStep(shown, places);
134
- places = Math.max(decimals(shown), decimals(to));
135
- // Tween would run the old tick for one more frame before the new
136
- // one starts; stop it on the step on screen first.
137
- if (!resting) void tween.set(shown, { duration: 0 });
138
- void tween.set(to, { duration: ms, easing: curve(valueEl, '--tick') });
139
- });
140
- });
141
- </script>
142
-
143
- <!--
144
- A caps label over a key figure in Geist with tabular figures (BRIEF, visual
145
- rules 3 and 4; da3-65). A number value ticks to a new value over
146
- `--dur-tick` on the `--tick` curve, never on the first render. The final
147
- value stays in the accessible text; the moving steps are aria-hidden. The
148
- root carries `data-ticking` during a tick. Extra attributes and `class` go
149
- to the <dl>.
150
- -->
151
- <dl
152
- {...rest}
153
- class={['hp-stat', className]}
154
- data-variant={variant}
155
- data-tone={tone}
156
- data-ticking={ticking ? '' : undefined}
157
- >
158
- <dt class="hp-stat-label hp-label">{label}</dt>
159
- <dd
160
- bind:this={valueEl}
161
- class={['hp-stat-value', variant === 'display' ? 'hp-display' : 'hp-page-title hp-num']}
162
- >
163
- <span class="hp-stat-figure"
164
- ><span class="hp-stat-final">{text}</span>{#if ticking}<span
165
- class="hp-stat-from"
166
- aria-hidden="true">{from}</span
167
- ><span class="hp-stat-step" aria-hidden="true">{step}</span>{/if}</span
168
- >{#if unit}{' '}<span class="hp-stat-unit">{unit}</span>{/if}
169
- </dd>
170
- {#if change !== undefined && change !== ''}
171
- <dd class="hp-stat-delta">
172
- <span class="hp-stat-change">{change}</span>{#if deltaNote}{' '}{deltaNote}{/if}
173
- </dd>
174
- {/if}
175
- </dl>
176
-
177
- <style>
178
- .hp-stat {
179
- display: grid;
180
- align-content: start;
181
- gap: var(--space-1);
182
- min-width: 0;
183
- }
184
- .hp-stat[data-variant='display'] {
185
- gap: var(--space-2);
186
- }
187
-
188
- .hp-stat-label {
189
- color: var(--muted);
190
- }
191
-
192
- /* Geist even inside a mono context such as a table cell. The size comes
193
- from base.css: .hp-page-title (24px, 20px on compact) with .hp-num, or
194
- .hp-display. A long figure breaks rather than widen a phone page. */
195
- .hp-stat-value {
196
- font-family: var(--sans);
197
- overflow-wrap: anywhere;
198
- }
199
-
200
- /* During a tick the figure stacks three layers in one grid cell: the
201
- final value (transparent, read by screen readers), a hidden copy of
202
- the figure shown when the tick began, and the moving step. The cell is
203
- as wide as the wider of the old and new figure, so the unit after it
204
- holds still; the steps lie between the two in value, and with tabular
205
- figures in width too. */
206
- .hp-stat[data-ticking] .hp-stat-figure {
207
- display: inline-grid;
208
- }
209
- .hp-stat[data-ticking] .hp-stat-figure > * {
210
- grid-area: 1 / 1;
211
- }
212
- .hp-stat[data-ticking] .hp-stat-final {
213
- opacity: 0;
214
- }
215
- .hp-stat-from {
216
- visibility: hidden;
217
- }
218
-
219
- .hp-stat-unit {
220
- color: var(--muted);
221
- font-size: var(--type-body);
222
- font-weight: 400;
223
- letter-spacing: 0;
224
- }
225
- .hp-stat[data-variant='display'] .hp-stat-unit {
226
- font-size: var(--type-section);
227
- }
228
-
229
- /* 16px on touch, the dense 14px on fine pointers. */
230
- .hp-stat-delta {
231
- color: var(--muted);
232
- font-size: var(--type-body);
233
- line-height: 24px;
234
- font-variant-numeric: tabular-nums;
235
- overflow-wrap: anywhere;
236
- }
237
- @media (pointer: fine) {
238
- .hp-stat-delta {
239
- font-size: var(--type-dense);
240
- line-height: 20px;
241
- }
242
- }
243
-
244
- .hp-stat[data-tone='ok'] .hp-stat-change {
245
- color: var(--ok);
246
- }
247
- .hp-stat[data-tone='warn'] .hp-stat-change {
248
- color: var(--warn);
249
- }
250
- .hp-stat[data-tone='danger'] .hp-stat-change {
251
- color: var(--danger);
252
- }
253
- </style>
1
+ <script lang="ts" module>
2
+ import type { HTMLAttributes } from 'svelte/elements';
3
+
4
+ /**
5
+ * `standard` (default): the size for a row of several stats. `display`:
6
+ * the 32 to 56px display size, for the view's one display subject.
7
+ */
8
+ export type StatVariant = 'standard' | 'display';
9
+
10
+ /** A status hue for the delta: `ok` good, `warn` needs attention, `danger` bad. */
11
+ export type StatTone = 'ok' | 'warn' | 'danger';
12
+
13
+ export type StatProps = Omit<HTMLAttributes<HTMLDListElement>, 'children'> & {
14
+ /** The caps label. A fixed string the app defines. */
15
+ label: string;
16
+ /**
17
+ * The value in Geist with tabular figures. A string is shown as given
18
+ * and changes at once. A number is shown through `format`; when it
19
+ * changes after the first render, it ticks from the shown figure to
20
+ * the new one (BRIEF, motion rule 5).
21
+ */
22
+ value: string | number;
23
+ /**
24
+ * Formats a number `value` at rest and at every step of a tick, for
25
+ * example `(n) => n.toLocaleString('en-US')`. Each step is rounded to
26
+ * the larger number of decimal places of the old and new value before
27
+ * it is formatted. Without `format` a number shows as `String(value)`,
28
+ * and the steps show those decimal places.
29
+ */
30
+ format?: (value: number) => string;
31
+ /** A unit after the value, smaller and in the muted ink: `EUR`, `ms`, `tasks`. */
32
+ unit?: string;
33
+ /**
34
+ * The change, in the muted ink. A number gets its sign: `+12`, `−3`
35
+ * (a true minus), `0`. A string is shown as written, sign included.
36
+ */
37
+ delta?: string | number;
38
+ /** What the delta compares with, after it: `since last week`. */
39
+ deltaNote?: string;
40
+ /**
41
+ * Colours the delta in a status hue. Without it the delta stays muted.
42
+ * The label or `deltaNote` must say the same, because colour is never
43
+ * the only signal.
44
+ */
45
+ tone?: StatTone;
46
+ variant?: StatVariant;
47
+ };
48
+ </script>
49
+
50
+ <script lang="ts">
51
+ import { untrack } from 'svelte';
52
+ import { Tween } from 'svelte/motion';
53
+ import { curve, duration, useMotion } from '../motion/index.js';
54
+
55
+ let {
56
+ label,
57
+ value,
58
+ format,
59
+ unit,
60
+ delta,
61
+ deltaNote,
62
+ tone,
63
+ variant = 'standard',
64
+ class: className,
65
+ ...rest
66
+ }: StatProps = $props();
67
+
68
+ const change = $derived(
69
+ typeof delta === 'number'
70
+ ? delta > 0
71
+ ? `+${delta}`
72
+ : delta < 0
73
+ ? `\u2212${-delta}`
74
+ : String(delta)
75
+ : delta
76
+ );
77
+
78
+ // ---- Tick (BRIEF, motion rule 5) ---------------------------------------
79
+
80
+ // A number value that changes after the first render ticks from the
81
+ // figure on screen to the new one. Quiet motion, so it runs in both tiers.
82
+ // Strings, reduced motion and live regions change at once.
83
+ const LIVE = '[aria-live]:not([aria-live="off"]), [role="status"], [role="log"], [role="alert"]';
84
+
85
+ const motion = useMotion();
86
+ let valueEl: HTMLElement | undefined;
87
+ // Starts on the first value, so nothing moves on the first render.
88
+ const tween = new Tween(untrack(() => (typeof value === 'number' ? value : 0)));
89
+ let last = untrack(() => value);
90
+ // The decimal places of the running tick's steps.
91
+ let places = $state(0);
92
+ // The figure on screen when the tick began. A hidden copy keeps its width.
93
+ let from = $state('');
94
+
95
+ /** The decimal places of `n` as `String()` writes it, exponent included. */
96
+ function decimals(n: number): number {
97
+ const [digits, exp = '0'] = String(n).split('e');
98
+ return Math.min(100, Math.max(0, (digits.split('.')[1]?.length ?? 0) - Number(exp)));
99
+ }
100
+ const atRest = (n: number) => (format ? format(n) : String(n));
101
+ /** One step: rounded to `p` places, never `-0`, then formatted. */
102
+ function atStep(n: number, p: number): string {
103
+ const r = Number(n.toFixed(p)) || 0;
104
+ return format ? format(r) : r.toFixed(p);
105
+ }
106
+
107
+ const text = $derived(typeof value === 'number' ? atRest(value) : value);
108
+ const ticking = $derived(
109
+ typeof value === 'number' && !Number.isNaN(value) && tween.current !== value
110
+ );
111
+ const step = $derived(ticking ? atStep(tween.current, places) : '');
112
+
113
+ // Before the DOM updates, so the first frame after a change still shows
114
+ // the old figure. Duration and curve are read from the value element at
115
+ // the change, so a retuned token applies. A change during a tick starts
116
+ // from the step on screen.
117
+ $effect.pre(() => {
118
+ const to = value;
119
+ untrack(() => {
120
+ const was = last;
121
+ last = to;
122
+ if (typeof to !== 'number' || to === was) return;
123
+ const resting = tween.current === tween.target;
124
+ const shown = resting ? tween.current : Number(tween.current.toFixed(places)) || 0;
125
+ const ms =
126
+ valueEl && typeof was === 'number' && !motion.reduced && !valueEl.closest(LIVE)
127
+ ? duration(valueEl, '--dur-tick')
128
+ : 0;
129
+ if (!valueEl || ms <= 0 || !Number.isFinite(shown) || !Number.isFinite(to)) {
130
+ void tween.set(to, { duration: 0 });
131
+ return;
132
+ }
133
+ from = resting ? atRest(shown) : atStep(shown, places);
134
+ places = Math.max(decimals(shown), decimals(to));
135
+ // Tween would run the old tick for one more frame before the new
136
+ // one starts; stop it on the step on screen first.
137
+ if (!resting) void tween.set(shown, { duration: 0 });
138
+ void tween.set(to, { duration: ms, easing: curve(valueEl, '--tick') });
139
+ });
140
+ });
141
+ </script>
142
+
143
+ <!--
144
+ A caps label over a key figure in Geist with tabular figures (BRIEF, visual
145
+ rules 3 and 4; da3-65). A number value ticks to a new value over
146
+ `--dur-tick` on the `--tick` curve, never on the first render. The final
147
+ value stays in the accessible text; the moving steps are aria-hidden. The
148
+ root carries `data-ticking` during a tick. Extra attributes and `class` go
149
+ to the <dl>.
150
+ -->
151
+ <dl
152
+ {...rest}
153
+ class={['hp-stat', className]}
154
+ data-variant={variant}
155
+ data-tone={tone}
156
+ data-ticking={ticking ? '' : undefined}
157
+ >
158
+ <dt class="hp-stat-label hp-label">{label}</dt>
159
+ <dd
160
+ bind:this={valueEl}
161
+ class={['hp-stat-value', variant === 'display' ? 'hp-display' : 'hp-page-title hp-num']}
162
+ >
163
+ <span class="hp-stat-figure"
164
+ ><span class="hp-stat-final">{text}</span>{#if ticking}<span
165
+ class="hp-stat-from"
166
+ aria-hidden="true">{from}</span
167
+ ><span class="hp-stat-step" aria-hidden="true">{step}</span>{/if}</span
168
+ >{#if unit}{' '}<span class="hp-stat-unit">{unit}</span>{/if}
169
+ </dd>
170
+ {#if change !== undefined && change !== ''}
171
+ <dd class="hp-stat-delta">
172
+ <span class="hp-stat-change">{change}</span>{#if deltaNote}{' '}{deltaNote}{/if}
173
+ </dd>
174
+ {/if}
175
+ </dl>
176
+
177
+ <style>
178
+ .hp-stat {
179
+ display: grid;
180
+ align-content: start;
181
+ gap: var(--space-1);
182
+ min-width: 0;
183
+ }
184
+ .hp-stat[data-variant='display'] {
185
+ gap: var(--space-2);
186
+ }
187
+
188
+ .hp-stat-label {
189
+ color: var(--muted);
190
+ }
191
+
192
+ /* Geist even inside a mono context such as a table cell. The size comes
193
+ from base.css: .hp-page-title (24px, 20px on compact) with .hp-num, or
194
+ .hp-display. A long figure breaks rather than widen a phone page. */
195
+ .hp-stat-value {
196
+ font-family: var(--sans);
197
+ overflow-wrap: anywhere;
198
+ }
199
+
200
+ /* During a tick the figure stacks three layers in one grid cell: the
201
+ final value (transparent, read by screen readers), a hidden copy of
202
+ the figure shown when the tick began, and the moving step. The cell is
203
+ as wide as the wider of the old and new figure, so the unit after it
204
+ holds still; the steps lie between the two in value, and with tabular
205
+ figures in width too. */
206
+ .hp-stat[data-ticking] .hp-stat-figure {
207
+ display: inline-grid;
208
+ }
209
+ .hp-stat[data-ticking] .hp-stat-figure > * {
210
+ grid-area: 1 / 1;
211
+ }
212
+ .hp-stat[data-ticking] .hp-stat-final {
213
+ opacity: 0;
214
+ }
215
+ .hp-stat-from {
216
+ visibility: hidden;
217
+ }
218
+
219
+ .hp-stat-unit {
220
+ color: var(--muted);
221
+ font-size: var(--type-body);
222
+ font-weight: 400;
223
+ letter-spacing: 0;
224
+ }
225
+ .hp-stat[data-variant='display'] .hp-stat-unit {
226
+ font-size: var(--type-section);
227
+ }
228
+
229
+ /* 16px on touch, the dense 14px on fine pointers. */
230
+ .hp-stat-delta {
231
+ color: var(--muted);
232
+ font-size: var(--type-body);
233
+ line-height: 24px;
234
+ font-variant-numeric: tabular-nums;
235
+ overflow-wrap: anywhere;
236
+ }
237
+ @media (pointer: fine) {
238
+ .hp-stat-delta {
239
+ font-size: var(--type-dense);
240
+ line-height: 20px;
241
+ }
242
+ }
243
+
244
+ .hp-stat[data-tone='ok'] .hp-stat-change {
245
+ color: var(--ok);
246
+ }
247
+ .hp-stat[data-tone='warn'] .hp-stat-change {
248
+ color: var(--warn);
249
+ }
250
+ .hp-stat[data-tone='danger'] .hp-stat-change {
251
+ color: var(--danger);
252
+ }
253
+ </style>