@lyeve-labs/ui-kit 0.39.1 → 0.41.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/README.md +5 -5
- package/dist/components/Field.svelte +12 -6
- package/dist/components/Field.svelte.d.ts +6 -2
- package/dist/components/FileInput.svelte +3 -1
- package/dist/components/GateNotice.svelte +68 -0
- package/dist/components/GateNotice.svelte.d.ts +20 -0
- package/dist/components/Panel.svelte +38 -8
- package/dist/components/Panel.svelte.d.ts +13 -0
- package/dist/components/RefusalNotice.svelte +61 -0
- package/dist/components/RefusalNotice.svelte.d.ts +29 -0
- package/dist/components/SegmentedControl.svelte +9 -3
- package/dist/components/SegmentedControl.svelte.d.ts +7 -2
- package/dist/components/SplitPane.svelte +428 -0
- package/dist/components/SplitPane.svelte.d.ts +46 -0
- package/dist/index.d.ts +5 -1
- package/dist/index.js +4 -1
- package/dist/internal/accept.d.ts +13 -0
- package/dist/internal/accept.js +76 -0
- package/dist/internal/aside.d.ts +36 -0
- package/dist/internal/aside.js +61 -0
- package/dist/internal/choice.d.ts +4 -2
- package/dist/internal/choice.js +2 -1
- package/dist/internal/gate.d.ts +71 -0
- package/dist/internal/gate.js +1 -0
- package/dist/internal/href.d.ts +8 -0
- package/dist/internal/href.js +18 -0
- package/dist/internal/icon.d.ts +12 -0
- package/dist/internal/icon.js +6 -0
- package/dist/internal/layout.d.ts +9 -0
- package/dist/internal/layout.js +9 -0
- package/dist/styles/theme.css +14 -0
- package/package.json +6 -2
- package/src/lib/styles/theme.css +14 -0
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What a split pane remembers per viewer: its width and whether the width is
|
|
3
|
+
* locked. Browser storage can be absent or throw (a private window, cleared or
|
|
4
|
+
* blocked site data, a thumbnail capture), so every read and write is guarded
|
|
5
|
+
* and the pane falls back to its default. A convenience, not state a server
|
|
6
|
+
* holds.
|
|
7
|
+
*
|
|
8
|
+
* The stored value is `{"width": <px>, "locked": <bool>}` under the key the
|
|
9
|
+
* consumer names, so a reader keeps the width an earlier copy of this pane
|
|
10
|
+
* wrote under the same key.
|
|
11
|
+
*
|
|
12
|
+
* Not exported from the package entry point: this is an implementation detail.
|
|
13
|
+
*/
|
|
14
|
+
/**
|
|
15
|
+
* The page's local storage, or nothing. Reading the global can itself throw
|
|
16
|
+
* when site data is blocked. Outside a browser it is never read: a server
|
|
17
|
+
* runtime may define a localStorage of its own, warning on first access or
|
|
18
|
+
* holding one store that every request would share.
|
|
19
|
+
*/
|
|
20
|
+
export function viewerStorage() {
|
|
21
|
+
if (typeof window === 'undefined')
|
|
22
|
+
return null;
|
|
23
|
+
try {
|
|
24
|
+
return typeof localStorage === 'undefined' ? null : localStorage;
|
|
25
|
+
}
|
|
26
|
+
catch {
|
|
27
|
+
return null;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
export function clampWidth(bounds, width) {
|
|
31
|
+
return Math.round(Math.min(bounds.max, Math.max(bounds.min, width)));
|
|
32
|
+
}
|
|
33
|
+
export function readAside(storage, key, bounds) {
|
|
34
|
+
const fallback = { width: bounds.width, locked: false };
|
|
35
|
+
try {
|
|
36
|
+
const raw = storage?.getItem(key);
|
|
37
|
+
if (!raw)
|
|
38
|
+
return fallback;
|
|
39
|
+
const parsed = JSON.parse(raw);
|
|
40
|
+
if (typeof parsed !== 'object' || parsed === null)
|
|
41
|
+
return fallback;
|
|
42
|
+
const { width, locked } = parsed;
|
|
43
|
+
return {
|
|
44
|
+
width: typeof width === 'number' && Number.isFinite(width)
|
|
45
|
+
? clampWidth(bounds, width)
|
|
46
|
+
: fallback.width,
|
|
47
|
+
locked: locked === true,
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
catch {
|
|
51
|
+
return fallback;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
export function writeAside(storage, key, bounds, prefs) {
|
|
55
|
+
try {
|
|
56
|
+
storage?.setItem(key, JSON.stringify({ width: clampWidth(bounds, prefs.width), locked: prefs.locked }));
|
|
57
|
+
}
|
|
58
|
+
catch {
|
|
59
|
+
// Storage refused: the width lasts the page and nothing else.
|
|
60
|
+
}
|
|
61
|
+
}
|
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
*
|
|
15
15
|
* Not exported from the package entry point: this is an implementation detail.
|
|
16
16
|
*/
|
|
17
|
+
import type { IconPx } from './icon.js';
|
|
17
18
|
/** The scale of the painted box: sm 14px, md 16px, lg 20px. */
|
|
18
19
|
export type ChoiceSize = 'sm' | 'md' | 'lg';
|
|
19
20
|
/** Inline is a box beside a label. Card is a bordered option surface. */
|
|
@@ -100,9 +101,10 @@ export declare function choiceIcon(size: ChoiceSize): string;
|
|
|
100
101
|
*
|
|
101
102
|
* A lucide icon takes a number, not a class, so the slot above cannot size it.
|
|
102
103
|
* These are the same 14, 16 and 20 the slot reserves. An icon rendered at any
|
|
103
|
-
* other size overflows the slot or floats inside it.
|
|
104
|
+
* other size overflows the slot or floats inside it. Each is a rung of the
|
|
105
|
+
* icon ladder, which the type holds.
|
|
104
106
|
*/
|
|
105
|
-
export declare const CHOICE_ICON_PX: Record<ChoiceSize,
|
|
107
|
+
export declare const CHOICE_ICON_PX: Record<ChoiceSize, IconPx>;
|
|
106
108
|
/**
|
|
107
109
|
* The fieldset a group renders, so the set is announced as a group rather than
|
|
108
110
|
* as loose controls.
|
package/dist/internal/choice.js
CHANGED
|
@@ -142,7 +142,8 @@ export function choiceIcon(size) {
|
|
|
142
142
|
*
|
|
143
143
|
* A lucide icon takes a number, not a class, so the slot above cannot size it.
|
|
144
144
|
* These are the same 14, 16 and 20 the slot reserves. An icon rendered at any
|
|
145
|
-
* other size overflows the slot or floats inside it.
|
|
145
|
+
* other size overflows the slot or floats inside it. Each is a rung of the
|
|
146
|
+
* icon ladder, which the type holds.
|
|
146
147
|
*/
|
|
147
148
|
export const CHOICE_ICON_PX = {
|
|
148
149
|
sm: 14,
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Why a read a page needed did not fill it, as the page learned it from the
|
|
3
|
+
* server's answer. A page never decides how a feature is enabled, which role a
|
|
4
|
+
* route needs or whether the database is up. It maps the answer to one of
|
|
5
|
+
* these states and hands it to GateNotice with its own words.
|
|
6
|
+
*/
|
|
7
|
+
export type Gate = {
|
|
8
|
+
state: 'ok';
|
|
9
|
+
}
|
|
10
|
+
/** The route is not there. */
|
|
11
|
+
| {
|
|
12
|
+
state: 'absent';
|
|
13
|
+
}
|
|
14
|
+
/** Not enabled here. The link forward is where it is turned on, when the server names one. */
|
|
15
|
+
| {
|
|
16
|
+
state: 'locked';
|
|
17
|
+
forwardHref: string;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* At a capacity ceiling: as many are in use as the server allows, so it
|
|
21
|
+
* refuses one more. The notice draws the page's words for it and offers no
|
|
22
|
+
* link, since only the page knows a way past the ceiling. The numbers are
|
|
23
|
+
* there for the page to put into those words, and either is null when the
|
|
24
|
+
* server did not send it.
|
|
25
|
+
*/
|
|
26
|
+
| {
|
|
27
|
+
state: 'full';
|
|
28
|
+
limit: number | null;
|
|
29
|
+
current: number | null;
|
|
30
|
+
}
|
|
31
|
+
/** The reader's role may not read it, which no retry changes. */
|
|
32
|
+
| {
|
|
33
|
+
state: 'forbidden';
|
|
34
|
+
}
|
|
35
|
+
/** Refused for the request rate. The same read succeeds once the window passes. */
|
|
36
|
+
| {
|
|
37
|
+
state: 'busy';
|
|
38
|
+
}
|
|
39
|
+
/** The server or its database did not answer. The same read may answer in a moment. */
|
|
40
|
+
| {
|
|
41
|
+
state: 'unavailable';
|
|
42
|
+
}
|
|
43
|
+
/** The read failed. The message says so in words that never read as an empty result. */
|
|
44
|
+
| {
|
|
45
|
+
state: 'error';
|
|
46
|
+
message: string;
|
|
47
|
+
};
|
|
48
|
+
/** A heading and the sentence under it. */
|
|
49
|
+
export interface GateText {
|
|
50
|
+
title: string;
|
|
51
|
+
body: string;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Every sentence a GateNotice can draw, in the page's own words. The kit holds
|
|
55
|
+
* none of them, so each console says a refusal its own way and in its own
|
|
56
|
+
* language.
|
|
57
|
+
*/
|
|
58
|
+
export interface GateWords {
|
|
59
|
+
/** What a missing route means for this page. */
|
|
60
|
+
absent: string;
|
|
61
|
+
/** A capacity ceiling, with the numbers the gate carried already in the body. */
|
|
62
|
+
full: GateText;
|
|
63
|
+
/** A role that may not read it, and who to ask. */
|
|
64
|
+
forbidden: string;
|
|
65
|
+
/** A refusal for the request rate. */
|
|
66
|
+
busy: GateText;
|
|
67
|
+
/** A server or database that did not answer. */
|
|
68
|
+
unavailable: GateText;
|
|
69
|
+
/** The label of the link that asks for the page again. */
|
|
70
|
+
retry: string;
|
|
71
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/internal/href.d.ts
CHANGED
|
@@ -11,3 +11,11 @@
|
|
|
11
11
|
*/
|
|
12
12
|
/** The href as given, or nothing when its scheme is one no component will emit. */
|
|
13
13
|
export declare function safeHref(href: string | undefined | null): string | undefined;
|
|
14
|
+
/**
|
|
15
|
+
* Whether an href leaves the page's own origin: an absolute URL with a scheme
|
|
16
|
+
* and a host, or a protocol-relative one, whose origin is not the page's. A
|
|
17
|
+
* link that leaves opens in a tab of its own, so the page it opens cannot
|
|
18
|
+
* reach back through window.opener. A server render has no location to
|
|
19
|
+
* compare with, so there every absolute URL counts as leaving.
|
|
20
|
+
*/
|
|
21
|
+
export declare function isExternalHref(href: string): boolean;
|
package/dist/internal/href.js
CHANGED
|
@@ -25,3 +25,21 @@ export function safeHref(href) {
|
|
|
25
25
|
return undefined;
|
|
26
26
|
return DENIED.test(href.replace(IGNORED, '')) ? undefined : href;
|
|
27
27
|
}
|
|
28
|
+
/**
|
|
29
|
+
* Whether an href leaves the page's own origin: an absolute URL with a scheme
|
|
30
|
+
* and a host, or a protocol-relative one, whose origin is not the page's. A
|
|
31
|
+
* link that leaves opens in a tab of its own, so the page it opens cannot
|
|
32
|
+
* reach back through window.opener. A server render has no location to
|
|
33
|
+
* compare with, so there every absolute URL counts as leaving.
|
|
34
|
+
*/
|
|
35
|
+
export function isExternalHref(href) {
|
|
36
|
+
const absolute = /^[a-z][a-z0-9+.-]*:\/\//i.test(href) || href.startsWith('//');
|
|
37
|
+
if (!absolute || typeof location === 'undefined')
|
|
38
|
+
return absolute;
|
|
39
|
+
try {
|
|
40
|
+
return new URL(href, location.href).origin !== location.origin;
|
|
41
|
+
}
|
|
42
|
+
catch {
|
|
43
|
+
return true;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The five sizes an icon may take. Each console names the same ladder in its
|
|
3
|
+
* own icon.ts, and the consistency suite holds every literal size in the kit
|
|
4
|
+
* to this list.
|
|
5
|
+
*/
|
|
6
|
+
export declare const ICON_LADDER: readonly [12, 14, 16, 20, 40];
|
|
7
|
+
/**
|
|
8
|
+
* A rung of the icon ladder. A size a component looks up in a table is typed
|
|
9
|
+
* with it, so an entry off the ladder fails the type check where the suite
|
|
10
|
+
* cannot read a number held in a variable.
|
|
11
|
+
*/
|
|
12
|
+
export type IconPx = (typeof ICON_LADDER)[number];
|
|
@@ -93,6 +93,15 @@ export declare const PAGE_STACK = "flex flex-col gap-section";
|
|
|
93
93
|
* crops the ring of every button inside it down to whichever edge fits.
|
|
94
94
|
*/
|
|
95
95
|
export declare const CARD_SURFACE = "bg-surface border border-line rounded-xl";
|
|
96
|
+
/**
|
|
97
|
+
* The card surface in the danger tone, for a group of destructive actions.
|
|
98
|
+
*
|
|
99
|
+
* The fill is danger mixed into surface rather than a translucent tint, so it
|
|
100
|
+
* is opaque and paints the same on the page ground and inside a card. Five
|
|
101
|
+
* percent keeps the controls of a form inside it on a ground they were drawn
|
|
102
|
+
* for. The contrast suite reads the share from this string.
|
|
103
|
+
*/
|
|
104
|
+
export declare const PANEL_DANGER_SURFACE = "bg-[color-mix(in_srgb,var(--color-danger)_5%,var(--color-surface))] border border-danger/40 rounded-xl";
|
|
96
105
|
/**
|
|
97
106
|
* Card padding by name.
|
|
98
107
|
*
|
package/dist/internal/layout.js
CHANGED
|
@@ -104,6 +104,15 @@ export const PAGE_STACK = 'flex flex-col gap-section';
|
|
|
104
104
|
* crops the ring of every button inside it down to whichever edge fits.
|
|
105
105
|
*/
|
|
106
106
|
export const CARD_SURFACE = 'bg-surface border border-line rounded-xl';
|
|
107
|
+
/**
|
|
108
|
+
* The card surface in the danger tone, for a group of destructive actions.
|
|
109
|
+
*
|
|
110
|
+
* The fill is danger mixed into surface rather than a translucent tint, so it
|
|
111
|
+
* is opaque and paints the same on the page ground and inside a card. Five
|
|
112
|
+
* percent keeps the controls of a form inside it on a ground they were drawn
|
|
113
|
+
* for. The contrast suite reads the share from this string.
|
|
114
|
+
*/
|
|
115
|
+
export const PANEL_DANGER_SURFACE = 'bg-[color-mix(in_srgb,var(--color-danger)_5%,var(--color-surface))] border border-danger/40 rounded-xl';
|
|
107
116
|
/**
|
|
108
117
|
* Card padding by name.
|
|
109
118
|
*
|
package/dist/styles/theme.css
CHANGED
|
@@ -524,6 +524,12 @@ html[data-theme='light'] {
|
|
|
524
524
|
*
|
|
525
525
|
* Durations are not zeroed. An animation that finishes in 1ms still fires its
|
|
526
526
|
* animationend, so components that wait on one keep working.
|
|
527
|
+
*
|
|
528
|
+
* The View Transitions API animates pseudo-elements on the root, and `*`
|
|
529
|
+
* matches none of them, so a page transition still cross-faded. They take a
|
|
530
|
+
* rule of their own because a browser that does not know a pseudo-element
|
|
531
|
+
* drops the whole rule it appears in, and the element rule has to survive
|
|
532
|
+
* there.
|
|
527
533
|
*/
|
|
528
534
|
@media (prefers-reduced-motion: reduce) {
|
|
529
535
|
*,
|
|
@@ -534,6 +540,14 @@ html[data-theme='light'] {
|
|
|
534
540
|
transition-duration: 1ms !important;
|
|
535
541
|
scroll-behavior: auto !important;
|
|
536
542
|
}
|
|
543
|
+
|
|
544
|
+
::view-transition-group(*),
|
|
545
|
+
::view-transition-image-pair(*),
|
|
546
|
+
::view-transition-old(*),
|
|
547
|
+
::view-transition-new(*) {
|
|
548
|
+
animation-duration: 1ms !important;
|
|
549
|
+
animation-iteration-count: 1 !important;
|
|
550
|
+
}
|
|
537
551
|
}
|
|
538
552
|
}
|
|
539
553
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lyeve-labs/ui-kit",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.41.0",
|
|
4
4
|
"description": "A clean, accessible, palette-aware Svelte 5 component library. The design system behind LyEve.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "LyEve Labs <hello@lyeve.com>",
|
|
@@ -22,7 +22,9 @@
|
|
|
22
22
|
"lyeve"
|
|
23
23
|
],
|
|
24
24
|
"type": "module",
|
|
25
|
-
"sideEffects":
|
|
25
|
+
"sideEffects": [
|
|
26
|
+
"**/*.css"
|
|
27
|
+
],
|
|
26
28
|
"engines": {
|
|
27
29
|
"node": ">=24"
|
|
28
30
|
},
|
|
@@ -52,7 +54,9 @@
|
|
|
52
54
|
"access": "public"
|
|
53
55
|
},
|
|
54
56
|
"devDependencies": {
|
|
57
|
+
"@axe-core/playwright": "^4.10.1",
|
|
55
58
|
"@lucide/svelte": "^0.511.0",
|
|
59
|
+
"@playwright/experimental-ct-svelte": "1.58.2",
|
|
56
60
|
"@sveltejs/package": "^2.5.7",
|
|
57
61
|
"@sveltejs/vite-plugin-svelte": "^4.0.4",
|
|
58
62
|
"@testing-library/svelte": "^5.3.1",
|
package/src/lib/styles/theme.css
CHANGED
|
@@ -524,6 +524,12 @@ html[data-theme='light'] {
|
|
|
524
524
|
*
|
|
525
525
|
* Durations are not zeroed. An animation that finishes in 1ms still fires its
|
|
526
526
|
* animationend, so components that wait on one keep working.
|
|
527
|
+
*
|
|
528
|
+
* The View Transitions API animates pseudo-elements on the root, and `*`
|
|
529
|
+
* matches none of them, so a page transition still cross-faded. They take a
|
|
530
|
+
* rule of their own because a browser that does not know a pseudo-element
|
|
531
|
+
* drops the whole rule it appears in, and the element rule has to survive
|
|
532
|
+
* there.
|
|
527
533
|
*/
|
|
528
534
|
@media (prefers-reduced-motion: reduce) {
|
|
529
535
|
*,
|
|
@@ -534,6 +540,14 @@ html[data-theme='light'] {
|
|
|
534
540
|
transition-duration: 1ms !important;
|
|
535
541
|
scroll-behavior: auto !important;
|
|
536
542
|
}
|
|
543
|
+
|
|
544
|
+
::view-transition-group(*),
|
|
545
|
+
::view-transition-image-pair(*),
|
|
546
|
+
::view-transition-old(*),
|
|
547
|
+
::view-transition-new(*) {
|
|
548
|
+
animation-duration: 1ms !important;
|
|
549
|
+
animation-iteration-count: 1 !important;
|
|
550
|
+
}
|
|
537
551
|
}
|
|
538
552
|
}
|
|
539
553
|
|