@ternkit/tern 1.2.0 → 1.3.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.
- package/dist/goscript/index.svelte.d.ts +32 -2
- package/dist/goscript/index.svelte.js +257 -31
- package/dist/goscript/patch.d.ts +6 -0
- package/dist/goscript/patch.js +25 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/ui/Accordion.svelte +84 -0
- package/dist/ui/Accordion.svelte.d.ts +20 -0
- package/dist/ui/Alert.svelte +76 -0
- package/dist/ui/Alert.svelte.d.ts +12 -0
- package/dist/ui/Avatar.svelte +48 -0
- package/dist/ui/Avatar.svelte.d.ts +10 -0
- package/dist/ui/Badge.svelte +54 -0
- package/dist/ui/Badge.svelte.d.ts +10 -0
- package/dist/ui/Button.svelte +136 -0
- package/dist/ui/Button.svelte.d.ts +18 -0
- package/dist/ui/Card.svelte +74 -0
- package/dist/ui/Card.svelte.d.ts +18 -0
- package/dist/ui/Checkbox.svelte +100 -0
- package/dist/ui/Checkbox.svelte.d.ts +14 -0
- package/dist/ui/Dialog.svelte +160 -0
- package/dist/ui/Dialog.svelte.d.ts +20 -0
- package/dist/ui/Field.svelte +77 -0
- package/dist/ui/Field.svelte.d.ts +17 -0
- package/dist/ui/Input.svelte +61 -0
- package/dist/ui/Input.svelte.d.ts +7 -0
- package/dist/ui/Kbd.svelte +24 -0
- package/dist/ui/Kbd.svelte.d.ts +7 -0
- package/dist/ui/Menu.svelte +232 -0
- package/dist/ui/Menu.svelte.d.ts +22 -0
- package/dist/ui/Progress.svelte +73 -0
- package/dist/ui/Progress.svelte.d.ts +12 -0
- package/dist/ui/RadioGroup.svelte +105 -0
- package/dist/ui/RadioGroup.svelte.d.ts +21 -0
- package/dist/ui/Select.svelte +76 -0
- package/dist/ui/Select.svelte.d.ts +18 -0
- package/dist/ui/Separator.svelte +19 -0
- package/dist/ui/Separator.svelte.d.ts +7 -0
- package/dist/ui/Skeleton.svelte +29 -0
- package/dist/ui/Skeleton.svelte.d.ts +9 -0
- package/dist/ui/Spinner.svelte +42 -0
- package/dist/ui/Spinner.svelte.d.ts +8 -0
- package/dist/ui/Switch.svelte +90 -0
- package/dist/ui/Switch.svelte.d.ts +12 -0
- package/dist/ui/Tabs.svelte +112 -0
- package/dist/ui/Tabs.svelte.d.ts +19 -0
- package/dist/ui/Textarea.svelte +51 -0
- package/dist/ui/Textarea.svelte.d.ts +7 -0
- package/dist/ui/Toaster.svelte +146 -0
- package/dist/ui/Toaster.svelte.d.ts +6 -0
- package/dist/ui/Tooltip.svelte +77 -0
- package/dist/ui/Tooltip.svelte.d.ts +15 -0
- package/dist/ui/field.d.ts +10 -0
- package/dist/ui/field.js +8 -0
- package/dist/ui/index.d.ts +24 -0
- package/dist/ui/index.js +27 -0
- package/dist/ui/theme.css +151 -0
- package/dist/ui/toast.svelte.d.ts +19 -0
- package/dist/ui/toast.svelte.js +18 -0
- package/dist/vite/goscript.d.ts +14 -0
- package/dist/vite/goscript.js +45 -16
- package/dist/vite/index.js +5 -2
- package/package.json +7 -1
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { Snippet } from 'svelte';
|
|
3
|
+
|
|
4
|
+
type Props = {
|
|
5
|
+
/** The tooltip's text. It describes the trigger; don't put anything interactive in it. */
|
|
6
|
+
text: string;
|
|
7
|
+
side?: 'top' | 'bottom';
|
|
8
|
+
/** How long to wait before showing, in milliseconds. */
|
|
9
|
+
delay?: number;
|
|
10
|
+
/** The trigger, which must be focusable, such as a Button. It receives aria-describedby through the snippet's props. */
|
|
11
|
+
children: Snippet<[{ 'aria-describedby': string }]>;
|
|
12
|
+
};
|
|
13
|
+
let { text, side = 'top', delay = 300, children }: Props = $props();
|
|
14
|
+
const uid = $props.id();
|
|
15
|
+
let open = $state(false);
|
|
16
|
+
let timer: ReturnType<typeof setTimeout> | undefined;
|
|
17
|
+
|
|
18
|
+
const show = (now = false) => {
|
|
19
|
+
clearTimeout(timer);
|
|
20
|
+
if (now) open = true;
|
|
21
|
+
else timer = setTimeout(() => (open = true), delay);
|
|
22
|
+
};
|
|
23
|
+
const hide = () => {
|
|
24
|
+
clearTimeout(timer);
|
|
25
|
+
open = false;
|
|
26
|
+
};
|
|
27
|
+
</script>
|
|
28
|
+
|
|
29
|
+
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
|
30
|
+
<span
|
|
31
|
+
class="tern-tooltip"
|
|
32
|
+
onpointerenter={() => show()}
|
|
33
|
+
onpointerleave={hide}
|
|
34
|
+
onfocusin={() => show(true)}
|
|
35
|
+
onfocusout={hide}
|
|
36
|
+
onkeydown={(e) => e.key === 'Escape' && hide()}
|
|
37
|
+
>
|
|
38
|
+
{@render children({ 'aria-describedby': `tern-tooltip-${uid}` })}
|
|
39
|
+
<span role="tooltip" id="tern-tooltip-{uid}" class="tip {side}" class:open>{text}</span>
|
|
40
|
+
</span>
|
|
41
|
+
|
|
42
|
+
<style>
|
|
43
|
+
.tern-tooltip {
|
|
44
|
+
position: relative;
|
|
45
|
+
display: inline-flex;
|
|
46
|
+
}
|
|
47
|
+
.tip {
|
|
48
|
+
position: absolute;
|
|
49
|
+
left: 50%;
|
|
50
|
+
z-index: 60;
|
|
51
|
+
width: max-content;
|
|
52
|
+
max-width: 16rem;
|
|
53
|
+
padding: 0.35rem 0.55rem;
|
|
54
|
+
border-radius: var(--tern-radius-sm);
|
|
55
|
+
background: var(--tern-fg);
|
|
56
|
+
color: var(--tern-bg);
|
|
57
|
+
font-size: 0.75rem;
|
|
58
|
+
line-height: 1.35;
|
|
59
|
+
pointer-events: none;
|
|
60
|
+
opacity: 0;
|
|
61
|
+
visibility: hidden;
|
|
62
|
+
translate: -50% 0;
|
|
63
|
+
transition:
|
|
64
|
+
opacity var(--tern-duration),
|
|
65
|
+
visibility var(--tern-duration);
|
|
66
|
+
}
|
|
67
|
+
.top {
|
|
68
|
+
bottom: calc(100% + 0.4rem);
|
|
69
|
+
}
|
|
70
|
+
.bottom {
|
|
71
|
+
top: calc(100% + 0.4rem);
|
|
72
|
+
}
|
|
73
|
+
.open {
|
|
74
|
+
opacity: 1;
|
|
75
|
+
visibility: visible;
|
|
76
|
+
}
|
|
77
|
+
</style>
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { Snippet } from 'svelte';
|
|
2
|
+
type Props = {
|
|
3
|
+
/** The tooltip's text. It describes the trigger; don't put anything interactive in it. */
|
|
4
|
+
text: string;
|
|
5
|
+
side?: 'top' | 'bottom';
|
|
6
|
+
/** How long to wait before showing, in milliseconds. */
|
|
7
|
+
delay?: number;
|
|
8
|
+
/** The trigger, which must be focusable, such as a Button. It receives aria-describedby through the snippet's props. */
|
|
9
|
+
children: Snippet<[{
|
|
10
|
+
'aria-describedby': string;
|
|
11
|
+
}]>;
|
|
12
|
+
};
|
|
13
|
+
declare const Tooltip: import("svelte").Component<Props, {}, "">;
|
|
14
|
+
type Tooltip = ReturnType<typeof Tooltip>;
|
|
15
|
+
export default Tooltip;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/** What a Field tells the control inside it, so labels and messages are wired up. */
|
|
2
|
+
export interface FieldContext {
|
|
3
|
+
readonly id: string;
|
|
4
|
+
readonly describedBy: string | undefined;
|
|
5
|
+
readonly invalid: boolean;
|
|
6
|
+
readonly required: boolean;
|
|
7
|
+
readonly disabled: boolean;
|
|
8
|
+
}
|
|
9
|
+
export declare function setField(ctx: FieldContext): void;
|
|
10
|
+
export declare function getField(): FieldContext | undefined;
|
package/dist/ui/field.js
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export { default as Accordion } from './Accordion.svelte';
|
|
2
|
+
export { default as Alert } from './Alert.svelte';
|
|
3
|
+
export { default as Avatar } from './Avatar.svelte';
|
|
4
|
+
export { default as Badge } from './Badge.svelte';
|
|
5
|
+
export { default as Button } from './Button.svelte';
|
|
6
|
+
export { default as Card } from './Card.svelte';
|
|
7
|
+
export { default as Checkbox } from './Checkbox.svelte';
|
|
8
|
+
export { default as Dialog } from './Dialog.svelte';
|
|
9
|
+
export { default as Field } from './Field.svelte';
|
|
10
|
+
export { default as Input } from './Input.svelte';
|
|
11
|
+
export { default as Kbd } from './Kbd.svelte';
|
|
12
|
+
export { default as Menu } from './Menu.svelte';
|
|
13
|
+
export { default as Progress } from './Progress.svelte';
|
|
14
|
+
export { default as RadioGroup } from './RadioGroup.svelte';
|
|
15
|
+
export { default as Select } from './Select.svelte';
|
|
16
|
+
export { default as Separator } from './Separator.svelte';
|
|
17
|
+
export { default as Skeleton } from './Skeleton.svelte';
|
|
18
|
+
export { default as Spinner } from './Spinner.svelte';
|
|
19
|
+
export { default as Switch } from './Switch.svelte';
|
|
20
|
+
export { default as Tabs } from './Tabs.svelte';
|
|
21
|
+
export { default as Textarea } from './Textarea.svelte';
|
|
22
|
+
export { default as Toaster } from './Toaster.svelte';
|
|
23
|
+
export { default as Tooltip } from './Tooltip.svelte';
|
|
24
|
+
export { dismiss, toast, toasts, type Toast, type Tone } from './toast.svelte.js';
|
package/dist/ui/index.js
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
// Tern UI: accessible Svelte 5 components with Tern's own look. Import the
|
|
2
|
+
// theme once (import '@ternkit/tern/ui/theme.css') and the components where
|
|
3
|
+
// you use them. See docs/ui.md.
|
|
4
|
+
export { default as Accordion } from './Accordion.svelte';
|
|
5
|
+
export { default as Alert } from './Alert.svelte';
|
|
6
|
+
export { default as Avatar } from './Avatar.svelte';
|
|
7
|
+
export { default as Badge } from './Badge.svelte';
|
|
8
|
+
export { default as Button } from './Button.svelte';
|
|
9
|
+
export { default as Card } from './Card.svelte';
|
|
10
|
+
export { default as Checkbox } from './Checkbox.svelte';
|
|
11
|
+
export { default as Dialog } from './Dialog.svelte';
|
|
12
|
+
export { default as Field } from './Field.svelte';
|
|
13
|
+
export { default as Input } from './Input.svelte';
|
|
14
|
+
export { default as Kbd } from './Kbd.svelte';
|
|
15
|
+
export { default as Menu } from './Menu.svelte';
|
|
16
|
+
export { default as Progress } from './Progress.svelte';
|
|
17
|
+
export { default as RadioGroup } from './RadioGroup.svelte';
|
|
18
|
+
export { default as Select } from './Select.svelte';
|
|
19
|
+
export { default as Separator } from './Separator.svelte';
|
|
20
|
+
export { default as Skeleton } from './Skeleton.svelte';
|
|
21
|
+
export { default as Spinner } from './Spinner.svelte';
|
|
22
|
+
export { default as Switch } from './Switch.svelte';
|
|
23
|
+
export { default as Tabs } from './Tabs.svelte';
|
|
24
|
+
export { default as Textarea } from './Textarea.svelte';
|
|
25
|
+
export { default as Toaster } from './Toaster.svelte';
|
|
26
|
+
export { default as Tooltip } from './Tooltip.svelte';
|
|
27
|
+
export { dismiss, toast, toasts } from './toast.svelte.js';
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Tern UI theme. Import once, in your root layout:
|
|
3
|
+
*
|
|
4
|
+
* import '@ternkit/tern/ui/theme.css';
|
|
5
|
+
*
|
|
6
|
+
* Every component reads these custom properties, so a theme is a set of
|
|
7
|
+
* overrides on :root (or any element, for a part of the page). Dark mode
|
|
8
|
+
* follows the system, or set data-theme="dark" / "light" on <html>.
|
|
9
|
+
*/
|
|
10
|
+
:root {
|
|
11
|
+
color-scheme: light;
|
|
12
|
+
--tern-font: ui-sans-serif, system-ui, -apple-system, 'Segoe UI', Roboto, 'Helvetica Neue', sans-serif;
|
|
13
|
+
--tern-font-mono: ui-monospace, 'SF Mono', Menlo, Consolas, monospace;
|
|
14
|
+
|
|
15
|
+
/* Surfaces and ink */
|
|
16
|
+
--tern-bg: oklch(0.99 0.004 220);
|
|
17
|
+
--tern-surface: oklch(1 0 0);
|
|
18
|
+
--tern-surface-2: oklch(0.97 0.006 220);
|
|
19
|
+
--tern-fg: oklch(0.24 0.02 240);
|
|
20
|
+
--tern-muted: oklch(0.5 0.02 240);
|
|
21
|
+
--tern-border: oklch(0.9 0.01 230);
|
|
22
|
+
--tern-border-strong: oklch(0.8 0.015 230);
|
|
23
|
+
|
|
24
|
+
/* The accent: a deep sea teal */
|
|
25
|
+
--tern-accent: oklch(0.52 0.1 205);
|
|
26
|
+
--tern-accent-hover: oklch(0.46 0.1 205);
|
|
27
|
+
--tern-accent-fg: oklch(0.99 0.01 205);
|
|
28
|
+
--tern-accent-soft: oklch(0.95 0.03 205);
|
|
29
|
+
|
|
30
|
+
--tern-danger: oklch(0.55 0.19 25);
|
|
31
|
+
--tern-danger-hover: oklch(0.49 0.19 25);
|
|
32
|
+
--tern-danger-fg: oklch(0.99 0 0);
|
|
33
|
+
--tern-danger-soft: oklch(0.96 0.03 25);
|
|
34
|
+
--tern-success: oklch(0.55 0.13 155);
|
|
35
|
+
--tern-success-soft: oklch(0.96 0.04 155);
|
|
36
|
+
--tern-warning: oklch(0.6 0.13 75);
|
|
37
|
+
--tern-warning-soft: oklch(0.97 0.05 85);
|
|
38
|
+
|
|
39
|
+
--tern-ring: oklch(0.6 0.12 205);
|
|
40
|
+
--tern-radius: 10px;
|
|
41
|
+
--tern-radius-sm: 7px;
|
|
42
|
+
--tern-shadow: 0 1px 2px oklch(0.24 0.02 240 / 0.06), 0 1px 1px oklch(0.24 0.02 240 / 0.04);
|
|
43
|
+
--tern-shadow-lg: 0 12px 32px -8px oklch(0.24 0.02 240 / 0.18), 0 2px 6px oklch(0.24 0.02 240 / 0.08);
|
|
44
|
+
--tern-control-height: 2.25rem;
|
|
45
|
+
--tern-duration: 140ms;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
@media (prefers-color-scheme: dark) {
|
|
49
|
+
:root:not([data-theme='light']) {
|
|
50
|
+
color-scheme: dark;
|
|
51
|
+
--tern-bg: oklch(0.17 0.015 240);
|
|
52
|
+
--tern-surface: oklch(0.21 0.017 240);
|
|
53
|
+
--tern-surface-2: oklch(0.25 0.018 240);
|
|
54
|
+
--tern-fg: oklch(0.94 0.01 230);
|
|
55
|
+
--tern-muted: oklch(0.7 0.02 230);
|
|
56
|
+
--tern-border: oklch(0.32 0.02 240);
|
|
57
|
+
--tern-border-strong: oklch(0.42 0.02 240);
|
|
58
|
+
--tern-accent: oklch(0.72 0.11 200);
|
|
59
|
+
--tern-accent-hover: oklch(0.78 0.1 200);
|
|
60
|
+
--tern-accent-fg: oklch(0.18 0.03 220);
|
|
61
|
+
--tern-accent-soft: oklch(0.3 0.05 205);
|
|
62
|
+
--tern-danger: oklch(0.7 0.17 25);
|
|
63
|
+
--tern-danger-hover: oklch(0.76 0.15 25);
|
|
64
|
+
--tern-danger-fg: oklch(0.18 0.04 25);
|
|
65
|
+
--tern-danger-soft: oklch(0.3 0.07 25);
|
|
66
|
+
--tern-success: oklch(0.72 0.14 155);
|
|
67
|
+
--tern-success-soft: oklch(0.29 0.05 155);
|
|
68
|
+
--tern-warning: oklch(0.78 0.13 80);
|
|
69
|
+
--tern-warning-soft: oklch(0.31 0.05 80);
|
|
70
|
+
--tern-ring: oklch(0.75 0.11 200);
|
|
71
|
+
--tern-shadow: 0 1px 2px oklch(0 0 0 / 0.4);
|
|
72
|
+
--tern-shadow-lg: 0 16px 40px -8px oklch(0 0 0 / 0.6), 0 2px 8px oklch(0 0 0 / 0.4);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
:root[data-theme='dark'] {
|
|
77
|
+
color-scheme: dark;
|
|
78
|
+
--tern-bg: oklch(0.17 0.015 240);
|
|
79
|
+
--tern-surface: oklch(0.21 0.017 240);
|
|
80
|
+
--tern-surface-2: oklch(0.25 0.018 240);
|
|
81
|
+
--tern-fg: oklch(0.94 0.01 230);
|
|
82
|
+
--tern-muted: oklch(0.7 0.02 230);
|
|
83
|
+
--tern-border: oklch(0.32 0.02 240);
|
|
84
|
+
--tern-border-strong: oklch(0.42 0.02 240);
|
|
85
|
+
--tern-accent: oklch(0.72 0.11 200);
|
|
86
|
+
--tern-accent-hover: oklch(0.78 0.1 200);
|
|
87
|
+
--tern-accent-fg: oklch(0.18 0.03 220);
|
|
88
|
+
--tern-accent-soft: oklch(0.3 0.05 205);
|
|
89
|
+
--tern-danger: oklch(0.7 0.17 25);
|
|
90
|
+
--tern-danger-hover: oklch(0.76 0.15 25);
|
|
91
|
+
--tern-danger-fg: oklch(0.18 0.04 25);
|
|
92
|
+
--tern-danger-soft: oklch(0.3 0.07 25);
|
|
93
|
+
--tern-success: oklch(0.72 0.14 155);
|
|
94
|
+
--tern-success-soft: oklch(0.29 0.05 155);
|
|
95
|
+
--tern-warning: oklch(0.78 0.13 80);
|
|
96
|
+
--tern-warning-soft: oklch(0.31 0.05 80);
|
|
97
|
+
--tern-ring: oklch(0.75 0.11 200);
|
|
98
|
+
--tern-shadow: 0 1px 2px oklch(0 0 0 / 0.4);
|
|
99
|
+
--tern-shadow-lg: 0 16px 40px -8px oklch(0 0 0 / 0.6), 0 2px 8px oklch(0 0 0 / 0.4);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
@media (prefers-reduced-motion: reduce) {
|
|
103
|
+
:root {
|
|
104
|
+
--tern-duration: 0ms;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/* Base styles, opt-in with class="tern" on <body> or any container. */
|
|
109
|
+
.tern {
|
|
110
|
+
font-family: var(--tern-font);
|
|
111
|
+
color: var(--tern-fg);
|
|
112
|
+
background: var(--tern-bg);
|
|
113
|
+
line-height: 1.5;
|
|
114
|
+
-webkit-font-smoothing: antialiased;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/* Semantic tables: <table class="tern-table">. */
|
|
118
|
+
.tern-table {
|
|
119
|
+
width: 100%;
|
|
120
|
+
border-collapse: collapse;
|
|
121
|
+
font-size: 0.875rem;
|
|
122
|
+
}
|
|
123
|
+
.tern-table th {
|
|
124
|
+
text-align: start;
|
|
125
|
+
font-weight: 600;
|
|
126
|
+
color: var(--tern-muted);
|
|
127
|
+
font-size: 0.75rem;
|
|
128
|
+
text-transform: uppercase;
|
|
129
|
+
letter-spacing: 0.04em;
|
|
130
|
+
}
|
|
131
|
+
.tern-table th,
|
|
132
|
+
.tern-table td {
|
|
133
|
+
padding: 0.625rem 0.75rem;
|
|
134
|
+
border-bottom: 1px solid var(--tern-border);
|
|
135
|
+
}
|
|
136
|
+
.tern-table tbody tr:hover {
|
|
137
|
+
background: var(--tern-surface-2);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/* Hidden visually, still read by screen readers. */
|
|
141
|
+
.tern-sr-only {
|
|
142
|
+
position: absolute;
|
|
143
|
+
width: 1px;
|
|
144
|
+
height: 1px;
|
|
145
|
+
padding: 0;
|
|
146
|
+
margin: -1px;
|
|
147
|
+
overflow: hidden;
|
|
148
|
+
clip: rect(0, 0, 0, 0);
|
|
149
|
+
white-space: nowrap;
|
|
150
|
+
border: 0;
|
|
151
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export type Tone = 'neutral' | 'success' | 'danger' | 'warning';
|
|
2
|
+
export interface Toast {
|
|
3
|
+
id: number;
|
|
4
|
+
message: string;
|
|
5
|
+
description?: string;
|
|
6
|
+
tone: Tone;
|
|
7
|
+
/** How long it stays, in milliseconds; 0 keeps it until dismissed. */
|
|
8
|
+
duration: number;
|
|
9
|
+
}
|
|
10
|
+
export declare const toasts: Toast[];
|
|
11
|
+
/** Shows a toast and returns its id. */
|
|
12
|
+
export declare function toast(message: string, opts?: Partial<Omit<Toast, 'id' | 'message'>>): number;
|
|
13
|
+
export declare namespace toast {
|
|
14
|
+
var success: (message: string, opts?: Partial<Omit<Toast, "id" | "message" | "tone">>) => number;
|
|
15
|
+
var error: (message: string, opts?: Partial<Omit<Toast, "id" | "message" | "tone">>) => number;
|
|
16
|
+
var warning: (message: string, opts?: Partial<Omit<Toast, "id" | "message" | "tone">>) => number;
|
|
17
|
+
}
|
|
18
|
+
/** Removes a toast. */
|
|
19
|
+
export declare function dismiss(id: number): void;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
// Toasts: short messages that confirm what happened, shown by <Toaster />.
|
|
2
|
+
let next = 1;
|
|
3
|
+
export const toasts = $state([]);
|
|
4
|
+
/** Shows a toast and returns its id. */
|
|
5
|
+
export function toast(message, opts = {}) {
|
|
6
|
+
const t = { id: next++, message, tone: 'neutral', duration: opts.tone === 'danger' ? 8000 : 5000, ...opts };
|
|
7
|
+
toasts.push(t);
|
|
8
|
+
return t.id;
|
|
9
|
+
}
|
|
10
|
+
/** Removes a toast. */
|
|
11
|
+
export function dismiss(id) {
|
|
12
|
+
const i = toasts.findIndex((t) => t.id === id);
|
|
13
|
+
if (i >= 0)
|
|
14
|
+
toasts.splice(i, 1);
|
|
15
|
+
}
|
|
16
|
+
toast.success = (message, opts) => toast(message, { ...opts, tone: 'success' });
|
|
17
|
+
toast.error = (message, opts) => toast(message, { ...opts, tone: 'danger' });
|
|
18
|
+
toast.warning = (message, opts) => toast(message, { ...opts, tone: 'warning' });
|
package/dist/vite/goscript.d.ts
CHANGED
|
@@ -6,6 +6,20 @@ export declare function exportedNames(source: string): {
|
|
|
6
6
|
values: string[];
|
|
7
7
|
funcs: string[];
|
|
8
8
|
};
|
|
9
|
+
/**
|
|
10
|
+
* The TypeScript that replaces a live component's Go script. The markup
|
|
11
|
+
* sees each State, Derived and Show value, each Func (callable, with
|
|
12
|
+
* .pending, .error, .optimistic and .debounce) and live: the connection,
|
|
13
|
+
* pending state, bindings, and live.props, everything the parent passed,
|
|
14
|
+
* such as children and event handlers.
|
|
15
|
+
*/
|
|
16
|
+
export declare function liveBindings(id: string, values: string[], funcs: string[]): string;
|
|
17
|
+
/**
|
|
18
|
+
* Wraps a live component's markup so it renders once the component has
|
|
19
|
+
* state (at once after a server render, after the first message after a
|
|
20
|
+
* client-side navigation), and embeds the server-rendered state.
|
|
21
|
+
*/
|
|
22
|
+
export declare function wrapLiveMarkup(content: string): string;
|
|
9
23
|
/** Replaces <script lang="go"> with the JavaScript that binds it to Go. */
|
|
10
24
|
export declare function goScriptPreprocessor(getRoot: () => string): PreprocessorGroup;
|
|
11
25
|
/** Builds the Go code at startup and on changes. */
|
package/dist/vite/goscript.js
CHANGED
|
@@ -136,6 +136,46 @@ function initialState(root, id) {
|
|
|
136
136
|
return {};
|
|
137
137
|
}
|
|
138
138
|
}
|
|
139
|
+
/**
|
|
140
|
+
* The TypeScript that replaces a live component's Go script. The markup
|
|
141
|
+
* sees each State, Derived and Show value, each Func (callable, with
|
|
142
|
+
* .pending, .error, .optimistic and .debounce) and live: the connection,
|
|
143
|
+
* pending state, bindings, and live.props, everything the parent passed,
|
|
144
|
+
* such as children and event handlers.
|
|
145
|
+
*/
|
|
146
|
+
export function liveBindings(id, values, funcs) {
|
|
147
|
+
const taken = new Set([...values, ...funcs]);
|
|
148
|
+
let code = `
|
|
149
|
+
import { useLive } from '@ternkit/tern/goscript';
|
|
150
|
+
let __goProps = $props();
|
|
151
|
+
const __goUID = $props.id();
|
|
152
|
+
const __go = useLive(${JSON.stringify(id)}, __goUID, () => ({ ...__goProps }));
|
|
153
|
+
`;
|
|
154
|
+
if (!taken.has('live'))
|
|
155
|
+
code += '\tconst live = __go;\n';
|
|
156
|
+
for (const v of values)
|
|
157
|
+
code += `\tconst ${v} = $derived(__go.state[${JSON.stringify(v)}] as any);\n`;
|
|
158
|
+
for (const f of funcs)
|
|
159
|
+
code += `\tconst ${f}: any = __go.fn(${JSON.stringify(f)});\n`;
|
|
160
|
+
return code;
|
|
161
|
+
}
|
|
162
|
+
/** Top-level blocks that stay outside the markup's {#if}. */
|
|
163
|
+
const OUTSIDE = /<script\b[\s\S]*?<\/script>|<style\b[\s\S]*?<\/style>|<svelte:head\b[\s\S]*?<\/svelte:head>|<svelte:(?:options|window|document|body)\b[^>]*?\/>|<svelte:(window|document|body)\b[^>]*?>[\s\S]*?<\/svelte:\1>/g;
|
|
164
|
+
/**
|
|
165
|
+
* Wraps a live component's markup so it renders once the component has
|
|
166
|
+
* state (at once after a server render, after the first message after a
|
|
167
|
+
* client-side navigation), and embeds the server-rendered state.
|
|
168
|
+
*/
|
|
169
|
+
export function wrapLiveMarkup(content) {
|
|
170
|
+
const kept = [];
|
|
171
|
+
let markup = content.replace(OUTSIDE, (block) => {
|
|
172
|
+
kept.push(block);
|
|
173
|
+
// Keep line numbers for the markup's compile errors.
|
|
174
|
+
return '\n'.repeat(block.split('\n').length - 1);
|
|
175
|
+
});
|
|
176
|
+
markup = markup.trim() ? `{#if __go.loaded}${markup}{/if}` : markup;
|
|
177
|
+
return markup + '\n' + kept.join('\n') + '\n<svelte:head>{@html __go.stateScript}</svelte:head>\n';
|
|
178
|
+
}
|
|
139
179
|
/** Replaces <script lang="go"> with the JavaScript that binds it to Go. */
|
|
140
180
|
export function goScriptPreprocessor(getRoot) {
|
|
141
181
|
return {
|
|
@@ -146,30 +186,19 @@ export function goScriptPreprocessor(getRoot) {
|
|
|
146
186
|
const m = GO_SCRIPT.exec(content);
|
|
147
187
|
if (!m || !/(^|\s)live(\s|=|$)/.test(m[1]))
|
|
148
188
|
return;
|
|
149
|
-
return { code: content
|
|
189
|
+
return { code: wrapLiveMarkup(content) };
|
|
150
190
|
},
|
|
151
191
|
script({ content, attributes, filename }) {
|
|
152
192
|
if (attributes.lang !== 'go' || !filename)
|
|
153
193
|
return;
|
|
154
194
|
const root = getRoot();
|
|
155
|
-
if (!goScriptEnabled(root)) {
|
|
156
|
-
throw new Error(`${filename}: <script lang="go"> is experimental; enable it with "experimental": { "goScript": true } in tern.json`);
|
|
157
|
-
}
|
|
158
195
|
const id = componentID(root, filename);
|
|
159
196
|
const { values, funcs } = exportedNames(content);
|
|
160
197
|
const { lang: _lang, live, ...rest } = attributes;
|
|
161
|
-
if (live)
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
const __goUID = $props.id();
|
|
166
|
-
const __go = useLive(${JSON.stringify(id)}, __goUID, () => ({ ...__goProps }));
|
|
167
|
-
`;
|
|
168
|
-
for (const v of values)
|
|
169
|
-
code += `\tconst ${v} = $derived(__go.state[${JSON.stringify(v)}] as any);\n`;
|
|
170
|
-
for (const f of funcs)
|
|
171
|
-
code += `\tconst ${f} = (...args: unknown[]): any => __go.call(${JSON.stringify(f)}, ...args);\n`;
|
|
172
|
-
return { code, attributes: { ...rest, lang: 'ts' } };
|
|
198
|
+
if (live)
|
|
199
|
+
return { code: liveBindings(id, values, funcs), attributes: { ...rest, lang: 'ts' } };
|
|
200
|
+
if (!goScriptEnabled(root)) {
|
|
201
|
+
throw new Error(`${filename}: <script lang="go"> in WebAssembly is experimental; enable it with "experimental": { "goScript": true } in tern.json, or add live to run it on the server`);
|
|
173
202
|
}
|
|
174
203
|
const base = path.join(root, DIR).split(path.sep).join('/');
|
|
175
204
|
let code = `
|
package/dist/vite/index.js
CHANGED
|
@@ -66,7 +66,7 @@ export function tern(options = {}) {
|
|
|
66
66
|
target: 'es2022',
|
|
67
67
|
rolldownOptions: {
|
|
68
68
|
input: source,
|
|
69
|
-
output: { format: 'iife', entryFileNames: 'service-worker.js' }
|
|
69
|
+
output: { format: 'iife', entryFileNames: 'service-worker.js', banner: '"use strict";' }
|
|
70
70
|
}
|
|
71
71
|
}
|
|
72
72
|
};
|
|
@@ -84,7 +84,10 @@ export function tern(options = {}) {
|
|
|
84
84
|
target: 'es2023',
|
|
85
85
|
rolldownOptions: {
|
|
86
86
|
input: path.join(root, SERVER_ENTRY),
|
|
87
|
-
|
|
87
|
+
// Strict, like the modules it is built from: the minifier's output
|
|
88
|
+
// assumes it, and in sloppy mode a renamed block-level function can
|
|
89
|
+
// shadow a variable (Annex B), which made bindings loop forever.
|
|
90
|
+
output: { format: 'iife', name: '__tern_server', entryFileNames: 'server.js', banner: '"use strict";' }
|
|
88
91
|
}
|
|
89
92
|
},
|
|
90
93
|
// One self-contained file: the renderer runs in an embedded engine
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ternkit/tern",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.3.0",
|
|
4
4
|
"description": "Client runtime and Vite plugin for Tern: Svelte on the client, Go on the server",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -34,6 +34,12 @@
|
|
|
34
34
|
"svelte": "./dist/goscript/index.svelte.js",
|
|
35
35
|
"default": "./dist/goscript/index.svelte.js"
|
|
36
36
|
},
|
|
37
|
+
"./ui": {
|
|
38
|
+
"types": "./dist/ui/index.d.ts",
|
|
39
|
+
"svelte": "./dist/ui/index.js",
|
|
40
|
+
"default": "./dist/ui/index.js"
|
|
41
|
+
},
|
|
42
|
+
"./ui/theme.css": "./dist/ui/theme.css",
|
|
37
43
|
"./server": {
|
|
38
44
|
"types": "./dist/server/index.d.ts",
|
|
39
45
|
"svelte": "./dist/server/index.js",
|