@ternkit/tern 1.3.0 → 1.5.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 (131) hide show
  1. package/dist/index.d.ts +2 -1
  2. package/dist/index.js +2 -1
  3. package/dist/runtime/deferred.d.ts +5 -1
  4. package/dist/runtime/deferred.js +27 -11
  5. package/dist/runtime/validate.d.ts +31 -0
  6. package/dist/runtime/validate.js +168 -0
  7. package/dist/server/intl.js +55 -12
  8. package/dist/ui/AlertDialog.svelte +79 -0
  9. package/dist/ui/AlertDialog.svelte.d.ts +24 -0
  10. package/dist/ui/AreaChart.svelte +9 -0
  11. package/dist/ui/AreaChart.svelte.d.ts +26 -0
  12. package/dist/ui/AspectRatio.svelte +33 -0
  13. package/dist/ui/AspectRatio.svelte.d.ts +10 -0
  14. package/dist/ui/BarChart.svelte +170 -0
  15. package/dist/ui/BarChart.svelte.d.ts +40 -0
  16. package/dist/ui/Breadcrumb.svelte +92 -0
  17. package/dist/ui/Breadcrumb.svelte.d.ts +14 -0
  18. package/dist/ui/ButtonGroup.svelte +61 -0
  19. package/dist/ui/ButtonGroup.svelte.d.ts +12 -0
  20. package/dist/ui/Calendar.svelte +265 -0
  21. package/dist/ui/Calendar.svelte.d.ts +28 -0
  22. package/dist/ui/Carousel.svelte +169 -0
  23. package/dist/ui/Carousel.svelte.d.ts +36 -0
  24. package/dist/ui/ChartFrame.svelte +174 -0
  25. package/dist/ui/ChartFrame.svelte.d.ts +24 -0
  26. package/dist/ui/Checkbox.svelte +13 -1
  27. package/dist/ui/Collapsible.svelte +59 -0
  28. package/dist/ui/Collapsible.svelte.d.ts +12 -0
  29. package/dist/ui/Combobox.svelte +350 -0
  30. package/dist/ui/Combobox.svelte.d.ts +24 -0
  31. package/dist/ui/Command.svelte +268 -0
  32. package/dist/ui/Command.svelte.d.ts +28 -0
  33. package/dist/ui/ConsentScreen.svelte +125 -0
  34. package/dist/ui/ConsentScreen.svelte.d.ts +21 -0
  35. package/dist/ui/ContextMenu.svelte +78 -0
  36. package/dist/ui/ContextMenu.svelte.d.ts +13 -0
  37. package/dist/ui/DataTable.svelte +277 -0
  38. package/dist/ui/DataTable.svelte.d.ts +53 -0
  39. package/dist/ui/DatePicker.svelte +138 -0
  40. package/dist/ui/DatePicker.svelte.d.ts +25 -0
  41. package/dist/ui/Dialog.svelte +168 -27
  42. package/dist/ui/Dialog.svelte.d.ts +10 -0
  43. package/dist/ui/DonutChart.svelte +115 -0
  44. package/dist/ui/DonutChart.svelte.d.ts +40 -0
  45. package/dist/ui/Drawer.svelte +19 -0
  46. package/dist/ui/Drawer.svelte.d.ts +14 -0
  47. package/dist/ui/Empty.svelte +62 -0
  48. package/dist/ui/Empty.svelte.d.ts +15 -0
  49. package/dist/ui/Field.svelte +22 -2
  50. package/dist/ui/Field.svelte.d.ts +2 -0
  51. package/dist/ui/Form.svelte +159 -0
  52. package/dist/ui/Form.svelte.d.ts +21 -0
  53. package/dist/ui/HoverCard.svelte +106 -0
  54. package/dist/ui/HoverCard.svelte.d.ts +26 -0
  55. package/dist/ui/Input.svelte +2 -1
  56. package/dist/ui/InputGroup.svelte +80 -0
  57. package/dist/ui/InputGroup.svelte.d.ts +13 -0
  58. package/dist/ui/InputOTP.svelte +140 -0
  59. package/dist/ui/InputOTP.svelte.d.ts +18 -0
  60. package/dist/ui/Item.svelte +86 -0
  61. package/dist/ui/Item.svelte.d.ts +17 -0
  62. package/dist/ui/Label.svelte +29 -0
  63. package/dist/ui/Label.svelte.d.ts +10 -0
  64. package/dist/ui/LineChart.svelte +171 -0
  65. package/dist/ui/LineChart.svelte.d.ts +42 -0
  66. package/dist/ui/Menu.svelte +22 -151
  67. package/dist/ui/Menu.svelte.d.ts +3 -10
  68. package/dist/ui/MenuList.svelte +317 -0
  69. package/dist/ui/MenuList.svelte.d.ts +29 -0
  70. package/dist/ui/Menubar.svelte +133 -0
  71. package/dist/ui/Menubar.svelte.d.ts +14 -0
  72. package/dist/ui/NavigationMenu.svelte +208 -0
  73. package/dist/ui/NavigationMenu.svelte.d.ts +18 -0
  74. package/dist/ui/Pagination.svelte +114 -0
  75. package/dist/ui/Pagination.svelte.d.ts +16 -0
  76. package/dist/ui/PasswordInput.svelte +134 -0
  77. package/dist/ui/PasswordInput.svelte.d.ts +11 -0
  78. package/dist/ui/Popover.svelte +127 -0
  79. package/dist/ui/Popover.svelte.d.ts +29 -0
  80. package/dist/ui/Resizable.svelte +122 -0
  81. package/dist/ui/Resizable.svelte.d.ts +16 -0
  82. package/dist/ui/ScrollArea.svelte +43 -0
  83. package/dist/ui/ScrollArea.svelte.d.ts +13 -0
  84. package/dist/ui/Select.svelte +3 -2
  85. package/dist/ui/SelectMenu.svelte +301 -0
  86. package/dist/ui/SelectMenu.svelte.d.ts +18 -0
  87. package/dist/ui/Sheet.svelte +21 -0
  88. package/dist/ui/Sheet.svelte.d.ts +16 -0
  89. package/dist/ui/Sidebar.svelte +263 -0
  90. package/dist/ui/Sidebar.svelte.d.ts +35 -0
  91. package/dist/ui/SignIn.svelte +182 -0
  92. package/dist/ui/SignIn.svelte.d.ts +38 -0
  93. package/dist/ui/SignUp.svelte +106 -0
  94. package/dist/ui/SignUp.svelte.d.ts +23 -0
  95. package/dist/ui/Slider.svelte +196 -0
  96. package/dist/ui/Slider.svelte.d.ts +19 -0
  97. package/dist/ui/Sparkline.svelte +23 -0
  98. package/dist/ui/Sparkline.svelte.d.ts +11 -0
  99. package/dist/ui/Stat.svelte +91 -0
  100. package/dist/ui/Stat.svelte.d.ts +18 -0
  101. package/dist/ui/Table.svelte +70 -0
  102. package/dist/ui/Table.svelte.d.ts +36 -0
  103. package/dist/ui/Textarea.svelte +2 -1
  104. package/dist/ui/Toggle.svelte +82 -0
  105. package/dist/ui/Toggle.svelte.d.ts +17 -0
  106. package/dist/ui/ToggleGroup.svelte +143 -0
  107. package/dist/ui/ToggleGroup.svelte.d.ts +27 -0
  108. package/dist/ui/Tooltip.svelte +27 -19
  109. package/dist/ui/Tooltip.svelte.d.ts +2 -1
  110. package/dist/ui/chart.d.ts +26 -0
  111. package/dist/ui/chart.js +89 -0
  112. package/dist/ui/dates.d.ts +13 -0
  113. package/dist/ui/dates.js +64 -0
  114. package/dist/ui/field.d.ts +8 -0
  115. package/dist/ui/floating.d.ts +44 -0
  116. package/dist/ui/floating.js +97 -0
  117. package/dist/ui/form.d.ts +12 -0
  118. package/dist/ui/form.js +8 -0
  119. package/dist/ui/index.d.ts +54 -0
  120. package/dist/ui/index.js +43 -0
  121. package/dist/ui/menu.d.ts +25 -0
  122. package/dist/ui/menu.js +1 -0
  123. package/dist/ui/options.d.ts +17 -0
  124. package/dist/ui/options.js +22 -0
  125. package/dist/ui/table.d.ts +26 -0
  126. package/dist/ui/table.js +1 -0
  127. package/dist/ui/theme.css +25 -0
  128. package/dist/vite/index.js +12 -3
  129. package/dist/vite/native.d.ts +3 -0
  130. package/dist/vite/native.js +122 -0
  131. package/package.json +3 -2
@@ -0,0 +1,169 @@
1
+ <script lang="ts" generics="T">
2
+ import type { Snippet } from 'svelte';
3
+
4
+ type Props = {
5
+ items: T[];
6
+ /** Names the carousel, such as "Featured projects". */
7
+ label: string;
8
+ /** Draws one slide. */
9
+ slide: Snippet<[T, number]>;
10
+ /** Slides visible at once on wide screens. */
11
+ perView?: number;
12
+ /** Moves on by itself every this many milliseconds, with a pause button. Stops on hover and focus. */
13
+ autoplay?: number;
14
+ class?: string;
15
+ };
16
+ let { items, label, slide, perView = 1, autoplay, class: klass = '' }: Props = $props();
17
+
18
+ const uid = $props.id();
19
+ let track = $state<HTMLDivElement>();
20
+ let current = $state(0);
21
+ // svelte-ignore state_referenced_locally
22
+ let playing = $state(!!autoplay);
23
+ let paused = $state(false); // by hover or focus
24
+
25
+ function go(i: number) {
26
+ const n = Math.max(0, items.length - perView);
27
+ current = ((i % (n + 1)) + n + 1) % (n + 1);
28
+ const el = track?.children[current] as HTMLElement | undefined;
29
+ track?.scrollTo({ left: el?.offsetLeft ?? 0, behavior: matchMedia('(prefers-reduced-motion: reduce)').matches ? 'auto' : 'smooth' });
30
+ }
31
+
32
+ $effect(() => {
33
+ if (!autoplay || !playing || paused) return;
34
+ const t = setInterval(() => go(current + 1), autoplay);
35
+ return () => clearInterval(t);
36
+ });
37
+
38
+ // Keep current in step when the user scrolls or swipes.
39
+ function onscroll() {
40
+ if (!track) return;
41
+ const w = (track.children[0] as HTMLElement | undefined)?.offsetWidth || 1;
42
+ current = Math.round(track.scrollLeft / w);
43
+ }
44
+ </script>
45
+
46
+ <section
47
+ class="tern-carousel {klass}"
48
+ aria-roledescription="carousel"
49
+ aria-label={label}
50
+ onpointerenter={() => (paused = true)}
51
+ onpointerleave={() => (paused = false)}
52
+ onfocusin={() => (paused = true)}
53
+ onfocusout={() => (paused = false)}
54
+ style:--per-view={perView}
55
+ >
56
+ <div class="controls">
57
+ {#if autoplay}
58
+ <button type="button" class="control" aria-label={playing ? 'Stop slides' : 'Start slides'} onclick={() => (playing = !playing)}>
59
+ {#if playing}
60
+ <svg aria-hidden="true" viewBox="0 0 16 16" width="14" height="14"><path d="M5 3.5v9M11 3.5v9" stroke="currentColor" stroke-width="2" stroke-linecap="round" /></svg>
61
+ {:else}
62
+ <svg aria-hidden="true" viewBox="0 0 16 16" width="14" height="14"><path d="M5 3.5l7 4.5-7 4.5z" fill="currentColor" /></svg>
63
+ {/if}
64
+ </button>
65
+ {/if}
66
+ <button type="button" class="control" aria-label="Previous slide" aria-controls="tern-carousel-{uid}" onclick={() => go(current - 1)}>
67
+ <svg aria-hidden="true" viewBox="0 0 16 16" width="16" height="16"><path d="M10 4l-4 4 4 4" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round" /></svg>
68
+ </button>
69
+ <button type="button" class="control" aria-label="Next slide" aria-controls="tern-carousel-{uid}" onclick={() => go(current + 1)}>
70
+ <svg aria-hidden="true" viewBox="0 0 16 16" width="16" height="16"><path d="M6 4l4 4-4 4" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round" /></svg>
71
+ </button>
72
+ </div>
73
+ <!-- svelte-ignore a11y_no_noninteractive_tabindex, a11y_no_noninteractive_element_interactions -->
74
+ <div
75
+ bind:this={track}
76
+ class="track"
77
+ id="tern-carousel-{uid}"
78
+ role="group"
79
+ aria-label="Slides"
80
+ tabindex="0"
81
+ aria-live={playing && !paused ? 'off' : 'polite'}
82
+ {onscroll}
83
+ onkeydown={(e) => {
84
+ if (e.key === 'ArrowRight' || e.key === 'ArrowLeft') {
85
+ e.preventDefault();
86
+ go(current + (e.key === 'ArrowRight' ? 1 : -1));
87
+ }
88
+ }}
89
+ >
90
+ {#each items as item, i (i)}
91
+ <div class="slide" role="group" aria-roledescription="slide" aria-label="{i + 1} of {items.length}" inert={i < current || i >= current + perView ? true : undefined}>
92
+ {@render slide(item, i)}
93
+ </div>
94
+ {/each}
95
+ </div>
96
+ <div class="dots" aria-hidden="true">
97
+ {#each { length: Math.max(1, items.length - perView + 1) }, i (i)}<span class:on={i === current}></span>{/each}
98
+ </div>
99
+ </section>
100
+
101
+ <style>
102
+ .tern-carousel {
103
+ display: grid;
104
+ gap: 0.75rem;
105
+ }
106
+ .controls {
107
+ display: flex;
108
+ justify-content: flex-end;
109
+ gap: 0.375rem;
110
+ }
111
+ .track {
112
+ display: flex;
113
+ gap: 1rem;
114
+ overflow-x: auto;
115
+ scroll-snap-type: x mandatory;
116
+ scrollbar-width: none;
117
+ border-radius: var(--tern-radius);
118
+ }
119
+ .track:focus-visible {
120
+ outline: 2px solid var(--tern-ring);
121
+ outline-offset: 2px;
122
+ }
123
+ .track::-webkit-scrollbar {
124
+ display: none;
125
+ }
126
+ .slide {
127
+ flex: 0 0 calc((100% - (var(--per-view) - 1) * 1rem) / var(--per-view));
128
+ scroll-snap-align: start;
129
+ min-width: 0;
130
+ }
131
+ @media (max-width: 40rem) {
132
+ .slide {
133
+ flex-basis: 85%;
134
+ }
135
+ }
136
+ .control {
137
+ display: grid;
138
+ place-items: center;
139
+ width: 2rem;
140
+ height: 2rem;
141
+ color: var(--tern-fg);
142
+ background: var(--tern-surface);
143
+ border: 1px solid var(--tern-border-strong);
144
+ border-radius: 50%;
145
+ cursor: pointer;
146
+ }
147
+ .control:hover {
148
+ background: var(--tern-surface-2);
149
+ }
150
+ .control:focus-visible {
151
+ outline: 2px solid var(--tern-ring);
152
+ outline-offset: 2px;
153
+ }
154
+ .dots {
155
+ display: flex;
156
+ justify-content: center;
157
+ gap: 0.375rem;
158
+ }
159
+ .dots span {
160
+ width: 0.4rem;
161
+ height: 0.4rem;
162
+ border-radius: 50%;
163
+ background: var(--tern-border-strong);
164
+ transition: background var(--tern-duration);
165
+ }
166
+ .dots span.on {
167
+ background: var(--tern-accent);
168
+ }
169
+ </style>
@@ -0,0 +1,36 @@
1
+ import type { Snippet } from 'svelte';
2
+ declare function $$render<T>(): {
3
+ props: {
4
+ items: T[];
5
+ /** Names the carousel, such as "Featured projects". */
6
+ label: string;
7
+ /** Draws one slide. */
8
+ slide: Snippet<[T, number]>;
9
+ /** Slides visible at once on wide screens. */
10
+ perView?: number;
11
+ /** Moves on by itself every this many milliseconds, with a pause button. Stops on hover and focus. */
12
+ autoplay?: number;
13
+ class?: string;
14
+ };
15
+ exports: {};
16
+ bindings: "";
17
+ slots: {};
18
+ events: {};
19
+ };
20
+ declare class __sveltets_Render<T> {
21
+ props(): ReturnType<typeof $$render<T>>['props'];
22
+ events(): ReturnType<typeof $$render<T>>['events'];
23
+ slots(): ReturnType<typeof $$render<T>>['slots'];
24
+ bindings(): "";
25
+ exports(): {};
26
+ }
27
+ interface $$IsomorphicComponent {
28
+ new <T>(options: import('svelte').ComponentConstructorOptions<ReturnType<__sveltets_Render<T>['props']>>): import('svelte').SvelteComponent<ReturnType<__sveltets_Render<T>['props']>, ReturnType<__sveltets_Render<T>['events']>, ReturnType<__sveltets_Render<T>['slots']>> & {
29
+ $$bindings?: ReturnType<__sveltets_Render<T>['bindings']>;
30
+ } & ReturnType<__sveltets_Render<T>['exports']>;
31
+ <T>(internal: unknown, props: ReturnType<__sveltets_Render<T>['props']> & {}): ReturnType<__sveltets_Render<T>['exports']>;
32
+ z_$$bindings?: ReturnType<__sveltets_Render<any>['bindings']>;
33
+ }
34
+ declare const Carousel: $$IsomorphicComponent;
35
+ type Carousel<T> = InstanceType<typeof Carousel<T>>;
36
+ export default Carousel;
@@ -0,0 +1,174 @@
1
+ <script lang="ts">
2
+ import type { Snippet } from 'svelte';
3
+ import Collapsible from './Collapsible.svelte';
4
+ import ScrollArea from './ScrollArea.svelte';
5
+
6
+ type Props = {
7
+ title: string;
8
+ description?: string;
9
+ /** The heading level, to fit the page's outline. */
10
+ level?: 2 | 3 | 4;
11
+ legend: { label: string; color: string; kind: 'line' | 'rect' }[];
12
+ /** The table view: every value, reachable without hovering. */
13
+ table: { headers: string[]; rows: string[][] };
14
+ /** What the live readout says for the focused point, for keyboard and screen reader users. */
15
+ readout: string;
16
+ children: Snippet;
17
+ class?: string;
18
+ };
19
+ let { title, description, level = 3, legend, table, readout, children, class: klass = '' }: Props = $props();
20
+ const uid = $props.id();
21
+ </script>
22
+
23
+ <figure class="tern-chart {klass}" aria-labelledby="tern-chart-{uid}-title">
24
+ <figcaption>
25
+ <svelte:element this={`h${level}`} class="title" id="tern-chart-{uid}-title">{title}</svelte:element>
26
+ {#if description}<p class="description">{description}</p>{/if}
27
+ </figcaption>
28
+ {#if legend.length >= 2}
29
+ <ul class="legend" aria-label="Legend">
30
+ {#each legend as item (item.label)}
31
+ <li><span class="key {item.kind}" style:background={item.color} aria-hidden="true"></span>{item.label}</li>
32
+ {/each}
33
+ </ul>
34
+ {/if}
35
+ {@render children()}
36
+ <p class="tern-sr-only" aria-live="polite">{readout}</p>
37
+ <Collapsible label="Show data">
38
+ <ScrollArea label="{title} data" maxHeight="16rem" class="table">
39
+ <table class="tern-table">
40
+ <caption class="tern-sr-only">{title}</caption>
41
+ <thead><tr>{#each table.headers as h, i (i)}<th scope="col" style:text-align={i ? 'end' : undefined}>{h}</th>{/each}</tr></thead>
42
+ <tbody>
43
+ {#each table.rows as row, r (r)}
44
+ <tr>{#each row as cell, i (i)}{#if i === 0}<th scope="row">{cell}</th>{:else}<td style:text-align="end">{cell}</td>{/if}{/each}</tr>
45
+ {/each}
46
+ </tbody>
47
+ </table>
48
+ </ScrollArea>
49
+ </Collapsible>
50
+ </figure>
51
+
52
+ <style>
53
+ .tern-chart {
54
+ display: grid;
55
+ grid-template-columns: minmax(0, 1fr);
56
+ gap: 0.5rem;
57
+ min-width: 0;
58
+ margin: 0;
59
+ color: var(--tern-fg);
60
+ }
61
+ .title {
62
+ margin: 0;
63
+ font-size: 0.9375rem;
64
+ font-weight: 600;
65
+ }
66
+ .description {
67
+ margin: 0.15rem 0 0;
68
+ font-size: 0.8125rem;
69
+ color: var(--tern-muted);
70
+ }
71
+ .legend {
72
+ display: flex;
73
+ flex-wrap: wrap;
74
+ gap: 0.25rem 1rem;
75
+ margin: 0;
76
+ padding: 0;
77
+ list-style: none;
78
+ font-size: 0.8125rem;
79
+ color: var(--tern-muted);
80
+ }
81
+ .legend li {
82
+ display: inline-flex;
83
+ align-items: center;
84
+ gap: 0.4rem;
85
+ }
86
+ .key.line {
87
+ width: 0.875rem;
88
+ height: 2px;
89
+ border-radius: 1px;
90
+ }
91
+ .key.rect {
92
+ width: 0.625rem;
93
+ height: 0.625rem;
94
+ border-radius: 2px;
95
+ }
96
+ .tern-chart :global(.table th[scope='row']) {
97
+ font-weight: 500;
98
+ text-transform: none;
99
+ letter-spacing: 0;
100
+ font-size: 0.875rem;
101
+ color: var(--tern-fg);
102
+ }
103
+ .tern-chart :global(.table td) {
104
+ font-variant-numeric: tabular-nums;
105
+ }
106
+ /* Shared by the charts' SVG. */
107
+ .tern-chart :global(.tern-plot) {
108
+ position: relative;
109
+ outline: none;
110
+ border-radius: var(--tern-radius-sm);
111
+ }
112
+ .tern-chart :global(.tern-plot:focus-visible) {
113
+ box-shadow: 0 0 0 2px var(--tern-ring);
114
+ }
115
+ .tern-chart :global(.tern-plot svg) {
116
+ display: block;
117
+ width: 100%;
118
+ overflow: visible;
119
+ }
120
+ .tern-chart :global(.grid) {
121
+ stroke: var(--tern-border);
122
+ stroke-width: 1;
123
+ shape-rendering: crispEdges;
124
+ }
125
+ .tern-chart :global(.tick) {
126
+ fill: var(--tern-muted);
127
+ font-size: 12px;
128
+ font-variant-numeric: tabular-nums;
129
+ }
130
+ .tern-chart :global(.direct) {
131
+ fill: var(--tern-fg);
132
+ font-size: 12px;
133
+ font-weight: 500;
134
+ }
135
+ .tern-chart :global(.tooltip) {
136
+ position: absolute;
137
+ top: 0;
138
+ z-index: 2;
139
+ display: grid;
140
+ gap: 0.2rem;
141
+ min-width: 8rem;
142
+ padding: 0.5rem 0.65rem;
143
+ font-size: 0.8125rem;
144
+ color: var(--tern-fg);
145
+ background: var(--tern-surface);
146
+ border: 1px solid var(--tern-border);
147
+ border-radius: var(--tern-radius-sm);
148
+ box-shadow: var(--tern-shadow-lg);
149
+ pointer-events: none;
150
+ white-space: nowrap;
151
+ }
152
+ .tern-chart :global(.tooltip .head) {
153
+ color: var(--tern-muted);
154
+ font-size: 0.75rem;
155
+ }
156
+ .tern-chart :global(.tooltip .row) {
157
+ display: flex;
158
+ align-items: center;
159
+ gap: 0.45rem;
160
+ }
161
+ .tern-chart :global(.tooltip .row strong) {
162
+ font-variant-numeric: tabular-nums;
163
+ font-weight: 650;
164
+ }
165
+ .tern-chart :global(.tooltip .row span) {
166
+ color: var(--tern-muted);
167
+ }
168
+ .tern-chart :global(.tooltip .swatch) {
169
+ width: 0.75rem;
170
+ height: 2px;
171
+ border-radius: 1px;
172
+ flex: none;
173
+ }
174
+ </style>
@@ -0,0 +1,24 @@
1
+ import type { Snippet } from 'svelte';
2
+ type Props = {
3
+ title: string;
4
+ description?: string;
5
+ /** The heading level, to fit the page's outline. */
6
+ level?: 2 | 3 | 4;
7
+ legend: {
8
+ label: string;
9
+ color: string;
10
+ kind: 'line' | 'rect';
11
+ }[];
12
+ /** The table view: every value, reachable without hovering. */
13
+ table: {
14
+ headers: string[];
15
+ rows: string[][];
16
+ };
17
+ /** What the live readout says for the focused point, for keyboard and screen reader users. */
18
+ readout: string;
19
+ children: Snippet;
20
+ class?: string;
21
+ };
22
+ declare const ChartFrame: import("svelte").Component<Props, {}, "">;
23
+ type ChartFrame = ReturnType<typeof ChartFrame>;
24
+ export default ChartFrame;
@@ -1,6 +1,7 @@
1
1
  <script lang="ts">
2
2
  import type { Snippet } from 'svelte';
3
3
  import type { HTMLInputAttributes } from 'svelte/elements';
4
+ import { getForm } from './form.js';
4
5
 
5
6
  type Props = Omit<HTMLInputAttributes, 'type'> & {
6
7
  checked?: boolean;
@@ -13,16 +14,21 @@
13
14
  };
14
15
  let { checked = $bindable(false), indeterminate = $bindable(false), children, description, class: klass = '', ...rest }: Props = $props();
15
16
  const uid = $props.id();
17
+ // In a Form, a named checkbox shows its own error, such as "Terms must be checked."
18
+ const form = getForm();
19
+ const error = $derived(typeof rest.name === 'string' ? form?.error(rest.name) : undefined);
20
+ const describedBy = $derived([description ? `tern-check-${uid}` : '', error ? `tern-check-${uid}-error` : ''].filter(Boolean).join(' ') || undefined);
16
21
  </script>
17
22
 
18
23
  <label class="tern-check {klass}">
19
- <input type="checkbox" {...rest} bind:checked bind:indeterminate aria-describedby={description ? `tern-check-${uid}` : undefined} />
24
+ <input type="checkbox" {...rest} bind:checked bind:indeterminate aria-describedby={describedBy} aria-invalid={error ? true : undefined} />
20
25
  <span class="box" aria-hidden="true">
21
26
  <svg viewBox="0 0 16 16"><path class="tick" d="M3.5 8.5l3 3 6-7" /><path class="dash" d="M4 8h8" /></svg>
22
27
  </span>
23
28
  <span class="text">
24
29
  {@render children?.()}
25
30
  {#if description}<span class="description" id="tern-check-{uid}">{description}</span>{/if}
31
+ {#if error}<span class="error" id="tern-check-{uid}-error" role="alert">{error}</span>{/if}
26
32
  </span>
27
33
  </label>
28
34
 
@@ -97,4 +103,10 @@
97
103
  color: var(--tern-muted);
98
104
  font-size: 0.8125rem;
99
105
  }
106
+ .error {
107
+ display: block;
108
+ font-size: 0.8125rem;
109
+ font-weight: 500;
110
+ color: var(--tern-danger);
111
+ }
100
112
  </style>
@@ -0,0 +1,59 @@
1
+ <script lang="ts">
2
+ import type { Snippet } from 'svelte';
3
+
4
+ type Props = {
5
+ /** Whether it's open; bind it. */
6
+ open?: boolean;
7
+ /** The button's text. */
8
+ label: string;
9
+ children: Snippet;
10
+ class?: string;
11
+ };
12
+ let { open = $bindable(false), label, children, class: klass = '' }: Props = $props();
13
+ const uid = $props.id();
14
+ </script>
15
+
16
+ <!-- A button that shows and hides a region: the disclosure pattern. -->
17
+ <div class="tern-collapsible {klass}">
18
+ <button type="button" aria-expanded={open} aria-controls="tern-collapsible-{uid}" onclick={() => (open = !open)}>
19
+ <svg aria-hidden="true" viewBox="0 0 16 16" width="14" height="14"><path d="M6 4l4 4-4 4" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round" /></svg>
20
+ {label}
21
+ </button>
22
+ <div id="tern-collapsible-{uid}" class="content" hidden={!open}>
23
+ {@render children()}
24
+ </div>
25
+ </div>
26
+
27
+ <style>
28
+ button {
29
+ display: inline-flex;
30
+ align-items: center;
31
+ gap: 0.375rem;
32
+ padding: 0.3rem 0.4rem 0.3rem 0.2rem;
33
+ font: inherit;
34
+ font-size: 0.875rem;
35
+ font-weight: 550;
36
+ color: var(--tern-fg);
37
+ background: transparent;
38
+ border: 0;
39
+ border-radius: var(--tern-radius-sm);
40
+ cursor: pointer;
41
+ }
42
+ button:hover {
43
+ background: var(--tern-surface-2);
44
+ }
45
+ button:focus-visible {
46
+ outline: 2px solid var(--tern-ring);
47
+ outline-offset: 1px;
48
+ }
49
+ svg {
50
+ color: var(--tern-muted);
51
+ transition: rotate var(--tern-duration);
52
+ }
53
+ [aria-expanded='true'] svg {
54
+ rotate: 90deg;
55
+ }
56
+ .content {
57
+ padding: 0.5rem 0 0 1.4rem;
58
+ }
59
+ </style>
@@ -0,0 +1,12 @@
1
+ import type { Snippet } from 'svelte';
2
+ type Props = {
3
+ /** Whether it's open; bind it. */
4
+ open?: boolean;
5
+ /** The button's text. */
6
+ label: string;
7
+ children: Snippet;
8
+ class?: string;
9
+ };
10
+ declare const Collapsible: import("svelte").Component<Props, {}, "open">;
11
+ type Collapsible = ReturnType<typeof Collapsible>;
12
+ export default Collapsible;