svelte-comp 1.0.9 → 1.1.2
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 +4 -4
- package/dist/App.svelte +555 -0
- package/dist/App.svelte.d.ts +3 -0
- package/dist/app.css +3 -0
- package/dist/lib/Carousel.svelte +6 -3
- package/dist/lib/CheckBox.svelte +1 -1
- package/dist/lib/CheckBox.svelte.d.ts +1 -1
- package/dist/lib/CodeView.svelte +26 -8
- package/dist/lib/FilePicker.svelte +1 -1
- package/dist/lib/Form.svelte +2 -2
- package/dist/lib/Hamburger.svelte +2 -2
- package/dist/lib/Hamburger.svelte.d.ts +1 -1
- package/dist/lib/Menu.svelte +59 -17
- package/dist/lib/PaginatedCard.svelte +1 -1
- package/dist/lib/Pagination.svelte +1 -1
- package/dist/lib/PrimaryColorSelect.svelte +99 -0
- package/dist/lib/PrimaryColorSelect.svelte.d.ts +9 -0
- package/dist/lib/ProgressCircle.svelte +1 -1
- package/dist/lib/Select.svelte +28 -6
- package/dist/lib/Splitter.svelte +1 -1
- package/dist/lib/Table.svelte +1 -1
- package/dist/lib/Tabs.svelte +12 -8
- package/dist/lib/index.d.ts +1 -0
- package/dist/lib/index.js +1 -0
- package/dist/lib/lang.d.ts +9 -0
- package/dist/lib/lang.js +3 -0
- package/dist/lib/types/index.d.ts +7 -0
- package/dist/main.d.ts +4 -0
- package/dist/main.js +8 -0
- package/dist/styles.css +64 -1
- package/package.json +9 -5
package/dist/lib/CodeView.svelte
CHANGED
|
@@ -48,6 +48,7 @@
|
|
|
48
48
|
import "prismjs/components/prism-json";
|
|
49
49
|
import "prismjs/components/prism-python";
|
|
50
50
|
import "prismjs/themes/prism.css";
|
|
51
|
+
import { cx } from "../utils";
|
|
51
52
|
|
|
52
53
|
type Props = {
|
|
53
54
|
code?: string;
|
|
@@ -140,20 +141,27 @@
|
|
|
140
141
|
</script>
|
|
141
142
|
|
|
142
143
|
<div
|
|
143
|
-
class=
|
|
144
|
+
class={cx(
|
|
145
|
+
"w-full border border-[var(--border-color-default)] bg-[var(--color-bg-surface)]",
|
|
146
|
+
"text-[var(--color-text-default)]"
|
|
147
|
+
)}
|
|
144
148
|
>
|
|
145
149
|
{#if title}
|
|
146
150
|
<div
|
|
147
|
-
class=
|
|
148
|
-
|
|
149
|
-
|
|
151
|
+
class={cx(
|
|
152
|
+
"px-3 py-1 bg-[var(--color-bg-muted)] font-semibold uppercase flex items-center justify-between",
|
|
153
|
+
TEXT[sz]
|
|
154
|
+
)}
|
|
150
155
|
>
|
|
151
156
|
<div>{title}</div>
|
|
152
157
|
|
|
153
158
|
{#if showCopyButton}
|
|
154
159
|
<button
|
|
155
160
|
onclick={copyToClipboard}
|
|
156
|
-
class=
|
|
161
|
+
class={cx(
|
|
162
|
+
"px-3 py-0.5 text-xs rounded bg-[var(--color-primary)] text-white hover:opacity-[var(--opacity-hover)]",
|
|
163
|
+
"transition focus-visible:ring-2 focus-visible:ring-[var(--border-color-focus)] focus:outline-none"
|
|
164
|
+
)}
|
|
157
165
|
class:!bg-green-600={copied}
|
|
158
166
|
>
|
|
159
167
|
{copied ? "Copied" : "Copy"}
|
|
@@ -166,9 +174,11 @@
|
|
|
166
174
|
{#if showLineNumbers}
|
|
167
175
|
<div
|
|
168
176
|
bind:this={gutterEl}
|
|
169
|
-
class=
|
|
170
|
-
|
|
171
|
-
|
|
177
|
+
class={cx(
|
|
178
|
+
"select-none px-3 py-[12px] border-r border-[var(--border-color-default)]",
|
|
179
|
+
"text-[var(--color-text-muted)] text-right overflow-hidden",
|
|
180
|
+
"bg-[var(--color-bg-surface)] tabular-nums min-h-[180px] max-h-[480px]"
|
|
181
|
+
)}
|
|
172
182
|
>
|
|
173
183
|
{#each lines as _, i (i)}
|
|
174
184
|
<div class={LINE_HEIGHT[sz]}>{i + 1}</div>
|
|
@@ -252,6 +262,14 @@
|
|
|
252
262
|
box-sizing: border-box;
|
|
253
263
|
}
|
|
254
264
|
|
|
265
|
+
.cv-input:focus {
|
|
266
|
+
outline: none;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
.cv-input:focus-visible {
|
|
270
|
+
outline: none !important;
|
|
271
|
+
}
|
|
272
|
+
|
|
255
273
|
/* Prism */
|
|
256
274
|
.token.comment {
|
|
257
275
|
color: oklch(0.937 0.019 256 / 0.45);
|
package/dist/lib/Form.svelte
CHANGED
|
@@ -38,13 +38,13 @@
|
|
|
38
38
|
* @prop compact {boolean} - Enables denser sizing across controls
|
|
39
39
|
* @default false
|
|
40
40
|
*
|
|
41
|
-
* @note Initial value for each field: `value[name]`
|
|
41
|
+
* @note Initial value for each field: `value[name]` → `schema.default` → `''` (or `false` for checkboxes).
|
|
42
42
|
* @note `validateOn='input'|'blur'|'submit'` controls when validators run; built-in checks: `required`, `number`, and `email` regex.
|
|
43
43
|
* @note `when(form)` hides a field dynamically; hidden fields are skipped during validation.
|
|
44
44
|
* @note `Select` options are coerced to strings for the underlying control; provide string values if you rely on strict equality.
|
|
45
45
|
* @note Errors are rendered with stable `id`s and wired via `aria-describedby`; `invalid` flags are passed to inputs.
|
|
46
46
|
* @note `expose` provides `{ reset, submit, validate, getData }`; `validate` returns `Promise<boolean>`.
|
|
47
|
-
* @note `compact` reduces control sizes (`xs
|
|
47
|
+
* @note `compact` reduces control sizes (`xs→xs`, `sm→xs`, `md→sm`, `lg→md`, `xl→lg`) and centers labels where applicable.
|
|
48
48
|
*/
|
|
49
49
|
import Field from "./Field.svelte";
|
|
50
50
|
import Select from "./Select.svelte";
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
|
|
1
|
+
<!-- src/lib/Hamburger.svelte -->
|
|
2
2
|
<script lang="ts">
|
|
3
3
|
/**
|
|
4
4
|
* @component Hamburger
|
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
* @note Clicking outside the panel or pressing `Escape` closes the drawer.
|
|
33
33
|
* @note Focus moves to the first interactive element inside the panel, is trapped while open, and returns to the trigger on close.
|
|
34
34
|
* @note In controlled mode (`pressed` is defined), state changes are requested via `onOpenChange(open)`.
|
|
35
|
-
* @note When `menuItems` is empty, a
|
|
35
|
+
* @note When `menuItems` is empty, a "No items" placeholder is shown.
|
|
36
36
|
* @note The drawer uses `role=\"dialog\"` and `aria-modal=\"true\"`; the trigger reflects state via `aria-expanded`.
|
|
37
37
|
*/
|
|
38
38
|
import type { Snippet } from "svelte";
|
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
* @note Clicking outside the panel or pressing `Escape` closes the drawer.
|
|
31
31
|
* @note Focus moves to the first interactive element inside the panel, is trapped while open, and returns to the trigger on close.
|
|
32
32
|
* @note In controlled mode (`pressed` is defined), state changes are requested via `onOpenChange(open)`.
|
|
33
|
-
* @note When `menuItems` is empty, a
|
|
33
|
+
* @note When `menuItems` is empty, a "No items" placeholder is shown.
|
|
34
34
|
* @note The drawer uses `role=\"dialog\"` and `aria-modal=\"true\"`; the trigger reflects state via `aria-expanded`.
|
|
35
35
|
*/
|
|
36
36
|
import type { Snippet } from "svelte";
|
package/dist/lib/Menu.svelte
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
|
|
1
|
+
<!-- src/lib/Menu.svelte -->
|
|
2
2
|
<script lang="ts">
|
|
3
3
|
/**
|
|
4
4
|
* @component Menu
|
|
@@ -48,6 +48,7 @@
|
|
|
48
48
|
|
|
49
49
|
// Refs for focus control
|
|
50
50
|
let triggerRefs = $state<Record<string, HTMLButtonElement>>({});
|
|
51
|
+
let menuRefs = $state<Record<string, HTMLDivElement>>({});
|
|
51
52
|
let itemRefs = $state<Record<string, HTMLButtonElement>>({});
|
|
52
53
|
let subItemRefs = $state<Record<string, HTMLButtonElement>>({});
|
|
53
54
|
|
|
@@ -55,6 +56,7 @@
|
|
|
55
56
|
let menuTop = $state(0);
|
|
56
57
|
let menuLeft = $state(0);
|
|
57
58
|
|
|
59
|
+
let subMenuRefs = $state<Record<string, HTMLDivElement>>({});
|
|
58
60
|
let subMenuTop = $state(0);
|
|
59
61
|
let subMenuLeft = $state(0);
|
|
60
62
|
|
|
@@ -69,8 +71,10 @@
|
|
|
69
71
|
const navBase =
|
|
70
72
|
"flex items-stretch pl-2 gap-1 border-b relative z-10 bg-[var(--color-bg-surface)] text-[var(--color-text-default)] border-[var(--border-color-default)]";
|
|
71
73
|
|
|
74
|
+
const subMenuGutter = 8;
|
|
75
|
+
|
|
72
76
|
const topButtonBase =
|
|
73
|
-
"px-4 rounded-xs leading-none transition-colors
|
|
77
|
+
"px-4 rounded-xs leading-none transition-colors outline-none focus-visible:shadow-[inset_0_0_0_2px_var(--border-color-focus)]";
|
|
74
78
|
|
|
75
79
|
const topButtonActive =
|
|
76
80
|
"bg-[var(--color-bg-muted)] text-[var(--color-text-default)]";
|
|
@@ -78,10 +82,10 @@
|
|
|
78
82
|
"hover:bg-[var(--color-bg-muted)] text-[var(--color-text-default)]";
|
|
79
83
|
|
|
80
84
|
const menuStyle = $derived(
|
|
81
|
-
`position:fixed; top:${menuTop}px; left:${menuLeft}px; width:max-content;`
|
|
85
|
+
`position:fixed; top:${menuTop}px; left:${menuLeft}px; width:max-content; max-width:calc(100vw - 16px);`
|
|
82
86
|
);
|
|
83
87
|
const subMenuStyle = $derived(
|
|
84
|
-
`position:fixed; top:${subMenuTop}px; left:${subMenuLeft}px; width:max-content;`
|
|
88
|
+
`position:fixed; top:${subMenuTop}px; left:${subMenuLeft}px; width:max-content; max-width:calc(100vw - 16px);`
|
|
85
89
|
);
|
|
86
90
|
|
|
87
91
|
const textCls = $derived(TEXT[sz]);
|
|
@@ -136,16 +140,51 @@
|
|
|
136
140
|
}
|
|
137
141
|
|
|
138
142
|
// Positioning dropdown
|
|
139
|
-
function updateMenuPosition(
|
|
143
|
+
function updateMenuPosition(
|
|
144
|
+
triggerEl: HTMLElement,
|
|
145
|
+
menuEl?: HTMLElement | null
|
|
146
|
+
) {
|
|
140
147
|
const rect = triggerEl.getBoundingClientRect();
|
|
148
|
+
const menuWidth = Math.min(
|
|
149
|
+
menuEl?.getBoundingClientRect().width ?? rect.width,
|
|
150
|
+
window.innerWidth - 16
|
|
151
|
+
);
|
|
152
|
+
const spaceRight = window.innerWidth - rect.left;
|
|
153
|
+
const spaceLeft = rect.right;
|
|
154
|
+
const alignRight = spaceRight < menuWidth && spaceLeft > spaceRight;
|
|
155
|
+
const viewportLeft = window.scrollX;
|
|
156
|
+
const viewportRight = window.scrollX + window.innerWidth;
|
|
157
|
+
|
|
141
158
|
menuTop = rect.bottom + window.scrollY;
|
|
142
|
-
|
|
159
|
+
const targetLeft = alignRight
|
|
160
|
+
? rect.right + window.scrollX - menuWidth
|
|
161
|
+
: rect.left + window.scrollX;
|
|
162
|
+
const maxLeft = viewportRight - menuWidth;
|
|
163
|
+
menuLeft = Math.max(viewportLeft, Math.min(targetLeft, maxLeft));
|
|
143
164
|
}
|
|
144
165
|
|
|
145
|
-
function updateSubMenuPosition(
|
|
166
|
+
function updateSubMenuPosition(
|
|
167
|
+
parentItemEl: HTMLElement,
|
|
168
|
+
subMenuEl?: HTMLElement | null
|
|
169
|
+
) {
|
|
146
170
|
const rect = parentItemEl.getBoundingClientRect();
|
|
171
|
+
const subRect = subMenuEl?.getBoundingClientRect();
|
|
172
|
+
const subWidth = Math.min(
|
|
173
|
+
subRect?.width ?? rect.width,
|
|
174
|
+
window.innerWidth - 16
|
|
175
|
+
);
|
|
176
|
+
const spaceRight = window.innerWidth - rect.right;
|
|
177
|
+
const spaceLeft = rect.left;
|
|
178
|
+
const shouldFlipLeft = spaceRight < subWidth && spaceLeft > spaceRight;
|
|
179
|
+
|
|
147
180
|
subMenuTop = rect.top + window.scrollY;
|
|
148
|
-
|
|
181
|
+
const viewportLeft = window.scrollX;
|
|
182
|
+
const viewportRight = window.scrollX + window.innerWidth;
|
|
183
|
+
const targetLeft = shouldFlipLeft
|
|
184
|
+
? rect.left + window.scrollX - subWidth - subMenuGutter
|
|
185
|
+
: rect.right + window.scrollX + subMenuGutter;
|
|
186
|
+
const maxLeft = viewportRight - subWidth - subMenuGutter;
|
|
187
|
+
subMenuLeft = Math.max(viewportLeft, Math.min(targetLeft, maxLeft));
|
|
149
188
|
}
|
|
150
189
|
|
|
151
190
|
function firstActionIndex(actions: MenuAction[]) {
|
|
@@ -209,7 +248,7 @@
|
|
|
209
248
|
activeIndex = firstIndex;
|
|
210
249
|
const triggerEl = triggerRefs[menuItem.name];
|
|
211
250
|
if (triggerEl) {
|
|
212
|
-
updateMenuPosition(triggerEl);
|
|
251
|
+
updateMenuPosition(triggerEl, menuRefs[menuItem.name]);
|
|
213
252
|
}
|
|
214
253
|
if (focusFirst && firstIndex !== -1) {
|
|
215
254
|
focusMenuAction(menuItem, firstIndex);
|
|
@@ -221,7 +260,7 @@
|
|
|
221
260
|
openSub = actionId(parentAction);
|
|
222
261
|
const parentEl = itemRefs[actionId(parentAction)];
|
|
223
262
|
if (parentEl) {
|
|
224
|
-
updateSubMenuPosition(parentEl);
|
|
263
|
+
updateSubMenuPosition(parentEl, subMenuRefs[actionId(parentAction)]);
|
|
225
264
|
}
|
|
226
265
|
const firstIndex = focusFirst ? firstActionIndex(parentAction.submenu) : -1;
|
|
227
266
|
activeSubIndex = firstIndex;
|
|
@@ -352,10 +391,10 @@
|
|
|
352
391
|
if (open) {
|
|
353
392
|
const triggerEl = triggerRefs[open];
|
|
354
393
|
if (triggerEl) {
|
|
355
|
-
updateMenuPosition(triggerEl);
|
|
394
|
+
updateMenuPosition(triggerEl, menuRefs[open]);
|
|
356
395
|
|
|
357
396
|
const handleScrollResize = () => {
|
|
358
|
-
updateMenuPosition(triggerEl);
|
|
397
|
+
updateMenuPosition(triggerEl, menuRefs[open]);
|
|
359
398
|
};
|
|
360
399
|
|
|
361
400
|
window.addEventListener("scroll", handleScrollResize, true);
|
|
@@ -372,11 +411,12 @@
|
|
|
372
411
|
$effect(() => {
|
|
373
412
|
if (openSub) {
|
|
374
413
|
const itemEl = itemRefs[openSub];
|
|
414
|
+
const subEl = subMenuRefs[openSub];
|
|
375
415
|
if (itemEl) {
|
|
376
|
-
updateSubMenuPosition(itemEl);
|
|
416
|
+
updateSubMenuPosition(itemEl, subEl);
|
|
377
417
|
|
|
378
418
|
const handleScrollResize = () => {
|
|
379
|
-
updateSubMenuPosition(itemEl);
|
|
419
|
+
updateSubMenuPosition(itemEl, subMenuRefs[openSub]);
|
|
380
420
|
};
|
|
381
421
|
|
|
382
422
|
window.addEventListener("scroll", handleScrollResize, true);
|
|
@@ -438,6 +478,7 @@
|
|
|
438
478
|
|
|
439
479
|
<!-- Main Menu -->
|
|
440
480
|
<div
|
|
481
|
+
bind:this={menuRefs[menuItem.name]}
|
|
441
482
|
class={cx(
|
|
442
483
|
"fixed z-50 min-w-44 p-2 rounded-xs shadow-[0_2px_4px_var(--shadow-color)] ",
|
|
443
484
|
"border border-[var(--border-color-default)] bg-[var(--color-bg-surface)]"
|
|
@@ -462,6 +503,7 @@
|
|
|
462
503
|
class={cx(
|
|
463
504
|
"relative text-left rounded-xs transition-colors outline-none px-1.5 py-0.5 my-1 mr-1 min-w-full flex items-center",
|
|
464
505
|
"gap-3 hover:bg-[var(--color-bg-muted)] focus-visible:bg-[var(--color-bg-muted)]",
|
|
506
|
+
"focus-visible:shadow-[inset_0_0_0_2px_var(--border-color-focus)]",
|
|
465
507
|
textCls
|
|
466
508
|
)}
|
|
467
509
|
onmousedown={(e) => e.preventDefault()}
|
|
@@ -518,9 +560,10 @@
|
|
|
518
560
|
<!-- Sub Menu -->
|
|
519
561
|
{#if hasSubmenu(action) && openSub === actionId(action)}
|
|
520
562
|
<div
|
|
563
|
+
bind:this={subMenuRefs[actionId(action)]}
|
|
521
564
|
class={cx(
|
|
522
565
|
"fixed z-50 min-w-44 p-2 rounded-xs shadow-[0_2px_4px_var(--shadow-color)]",
|
|
523
|
-
"border border-[var(--border-color-default)] bg-[var(--color-bg-surface)]
|
|
566
|
+
"border border-[var(--border-color-default)] bg-[var(--color-bg-surface)]"
|
|
524
567
|
)}
|
|
525
568
|
style={subMenuStyle}
|
|
526
569
|
role="menu"
|
|
@@ -543,8 +586,7 @@
|
|
|
543
586
|
"relative text-left rounded-xs transition-colors outline-none px-1.5 py-0.5",
|
|
544
587
|
"my-1 mr-1 w-full flex items-center justify-between gap-3",
|
|
545
588
|
"hover:bg-[var(--color-bg-muted)] focus-visible:bg-[var(--color-bg-muted)]",
|
|
546
|
-
"focus-visible:
|
|
547
|
-
"focus-visible:ring-offset-2 focus-visible:ring-offset-[var(--color-bg-surface)]",
|
|
589
|
+
"focus-visible:shadow-[inset_0_0_0_2px_var(--border-color-focus)]",
|
|
548
590
|
"decoration-[var(--color-text-default)]",
|
|
549
591
|
textCls
|
|
550
592
|
)}
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
* @prop class {string} - Custom classes applied to the pagination wrapper
|
|
14
14
|
* @default ""
|
|
15
15
|
*
|
|
16
|
-
* @note Displays
|
|
16
|
+
* @note Displays “Page X of Y” and numbered page buttons.
|
|
17
17
|
* @note Prev/next buttons are disabled at the edges.
|
|
18
18
|
* @note Shows up to 3 numbered buttons centered around the current page.
|
|
19
19
|
* @note Uses `aria-current=\"page\"` on the active page for accessibility.
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
<!-- src/lib/PrimaryColorSelect.svelte -->
|
|
2
|
+
<script lang="ts">
|
|
3
|
+
/**
|
|
4
|
+
* @component PrimaryColorSelect
|
|
5
|
+
* @description Theme primary-color selector built on top of Select. Provides a fixed palette,
|
|
6
|
+
* handles persistence, and updates the global <html> attribute.
|
|
7
|
+
*
|
|
8
|
+
* @prop sz {SizeKey} - Sizing preset passed directly to Select
|
|
9
|
+
* @options xs|sm|md|lg|xl
|
|
10
|
+
* @default sm
|
|
11
|
+
*
|
|
12
|
+
* @prop label {string} - Custom label text. Falls back to localized copy when omitted.
|
|
13
|
+
*
|
|
14
|
+
* @prop class {string} - Extra classes forwarded to the underlying Select component
|
|
15
|
+
* @default ""
|
|
16
|
+
*
|
|
17
|
+
* @note The palette is predefined internally (`{ value, label, swatch }`).
|
|
18
|
+
* @note Selected value is stored in localStorage under "primary".
|
|
19
|
+
* @note The `html` element receives `data-primary="{value}"` for theme styling.
|
|
20
|
+
* @note Uses the same onChange contract as Select and forwards palette options as-is.
|
|
21
|
+
*/
|
|
22
|
+
import { getContext } from "svelte";
|
|
23
|
+
import Select from "./Select.svelte";
|
|
24
|
+
import type { PrimaryKey, PaletteOption, SizeKey } from "./types";
|
|
25
|
+
import { TEXTS } from "./lang";
|
|
26
|
+
|
|
27
|
+
type Props = {
|
|
28
|
+
sz?: SizeKey;
|
|
29
|
+
label?: string;
|
|
30
|
+
class?: string;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
let { sz = "sm", label, class: externalClass = "" }: Props = $props();
|
|
34
|
+
|
|
35
|
+
const langCtx =
|
|
36
|
+
getContext<{ value: keyof typeof TEXTS } | undefined>("lang") ?? null;
|
|
37
|
+
const langKey = $derived(langCtx?.value ?? "en");
|
|
38
|
+
const L = $derived(TEXTS[langKey].components.primaryColorSelect);
|
|
39
|
+
|
|
40
|
+
const labelFinal = $derived(label ?? L.text);
|
|
41
|
+
|
|
42
|
+
const palette: PaletteOption[] = [
|
|
43
|
+
{
|
|
44
|
+
value: "default",
|
|
45
|
+
label: "Indigo",
|
|
46
|
+
swatch: "oklch(62.3% 0.214 259.8deg)",
|
|
47
|
+
},
|
|
48
|
+
{ value: "cyan", label: "Cyan", swatch: "oklch(71.5% 0.143 215.221)" },
|
|
49
|
+
{ value: "red", label: "Red", swatch: "oklch(58% 0.24 30deg)" },
|
|
50
|
+
{ value: "green", label: "Green", swatch: "oklch(65% 0.22 140deg)" },
|
|
51
|
+
{ value: "yellow", label: "Yellow", swatch: "oklch(75% 0.18 90deg)" },
|
|
52
|
+
{ value: "pink", label: "Pink", swatch: "oklch(70% 0.25 350deg)" },
|
|
53
|
+
{ value: "orange", label: "Orange", swatch: "oklch(72% 0.22 60deg)" },
|
|
54
|
+
{ value: "purple", label: "Purple", swatch: "oklch(55% 0.22 290deg)" },
|
|
55
|
+
];
|
|
56
|
+
|
|
57
|
+
const paletteMap = palette.reduce<Record<PrimaryKey, PaletteOption>>(
|
|
58
|
+
(acc, option) => {
|
|
59
|
+
acc[option.value] = option;
|
|
60
|
+
return acc;
|
|
61
|
+
},
|
|
62
|
+
{} as Record<PrimaryKey, PaletteOption>
|
|
63
|
+
);
|
|
64
|
+
|
|
65
|
+
let selected = $state<PrimaryKey>("default");
|
|
66
|
+
let mounted = $state(false);
|
|
67
|
+
|
|
68
|
+
function isPrimaryKey(value: unknown): value is PrimaryKey {
|
|
69
|
+
return typeof value === "string" && value in paletteMap;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
$effect(() => {
|
|
73
|
+
if (!mounted) {
|
|
74
|
+
const saved = localStorage.getItem("primary");
|
|
75
|
+
if (isPrimaryKey(saved)) {
|
|
76
|
+
selected = saved;
|
|
77
|
+
}
|
|
78
|
+
mounted = true;
|
|
79
|
+
}
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
$effect(() => {
|
|
83
|
+
if (mounted) {
|
|
84
|
+
document.documentElement.setAttribute("data-primary", selected);
|
|
85
|
+
localStorage.setItem("primary", selected);
|
|
86
|
+
}
|
|
87
|
+
});
|
|
88
|
+
</script>
|
|
89
|
+
|
|
90
|
+
<Select
|
|
91
|
+
{sz}
|
|
92
|
+
label={labelFinal}
|
|
93
|
+
options={palette}
|
|
94
|
+
value={selected}
|
|
95
|
+
onChange={(v) => {
|
|
96
|
+
if (isPrimaryKey(v)) selected = v;
|
|
97
|
+
}}
|
|
98
|
+
class={externalClass}
|
|
99
|
+
/>
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { SizeKey } from "./types";
|
|
2
|
+
type Props = {
|
|
3
|
+
sz?: SizeKey;
|
|
4
|
+
label?: string;
|
|
5
|
+
class?: string;
|
|
6
|
+
};
|
|
7
|
+
declare const PrimaryColorSelect: import("svelte").Component<Props, {}, "">;
|
|
8
|
+
type PrimaryColorSelect = ReturnType<typeof PrimaryColorSelect>;
|
|
9
|
+
export default PrimaryColorSelect;
|
package/dist/lib/Select.svelte
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
|
|
1
|
+
<!-- src/lib/Select.svelte -->
|
|
2
2
|
<script lang="ts">
|
|
3
3
|
/**
|
|
4
4
|
* @component Select
|
|
@@ -167,6 +167,8 @@
|
|
|
167
167
|
`position:fixed;top:${menuTop}px;left:${menuLeft}px;min-width:${menuWidth}px;max-height:${menuMaxHeight}px;`
|
|
168
168
|
);
|
|
169
169
|
|
|
170
|
+
const selectedOption = $derived(options.find((o) => o.value === value));
|
|
171
|
+
|
|
170
172
|
$effect(() => {
|
|
171
173
|
const currentTriggerEl = triggerEl;
|
|
172
174
|
const currentListEl = listEl;
|
|
@@ -409,14 +411,25 @@
|
|
|
409
411
|
onkeydown={onTriggerKeydown}
|
|
410
412
|
>
|
|
411
413
|
<span class="min-w-0 grow truncate">
|
|
412
|
-
{#if
|
|
413
|
-
|
|
414
|
+
{#if selectedOption}
|
|
415
|
+
<span class="inline-flex items-center gap-2 min-w-0">
|
|
416
|
+
{#if selectedOption.swatch}
|
|
417
|
+
<span
|
|
418
|
+
aria-hidden="true"
|
|
419
|
+
class="block w-3 h-3 rounded-[var(--radius-xs)] border border-[var(--border-color-default)] shadow-sm shrink-0"
|
|
420
|
+
style={`background:${selectedOption.swatch}`}
|
|
421
|
+
></span>
|
|
422
|
+
{/if}
|
|
423
|
+
<span class="truncate">{selectedOption.label}</span>
|
|
424
|
+
</span>
|
|
414
425
|
{:else}
|
|
415
426
|
<span class="[color:var(--color-text-muted)]">{placeholder}</span>
|
|
416
427
|
{/if}
|
|
417
428
|
</span>
|
|
418
429
|
<span
|
|
419
|
-
class={cx(
|
|
430
|
+
class={cx(
|
|
431
|
+
"pointer-events-none absolute inset-y-0 right-0 flex items-center pr-2 [color:var(--color-text-default)]"
|
|
432
|
+
)}
|
|
420
433
|
>
|
|
421
434
|
<svg
|
|
422
435
|
class={iconsSizes[sz]}
|
|
@@ -470,13 +483,22 @@
|
|
|
470
483
|
<button
|
|
471
484
|
type="button"
|
|
472
485
|
tabindex="0"
|
|
473
|
-
class={cx(
|
|
486
|
+
class={cx(
|
|
487
|
+
"w-full text-left focus:outline-[3px] focus:outline-offset-3 focus:outline-[var(--border-color-focus)] rounded flex items-center gap-2"
|
|
488
|
+
)}
|
|
474
489
|
disabled={opt.disabled}
|
|
475
490
|
onclick={() => choose(i)}
|
|
476
491
|
onfocus={() => (highlighted = i)}
|
|
477
492
|
onmouseenter={() => (highlighted = i)}
|
|
478
493
|
>
|
|
479
|
-
{opt.
|
|
494
|
+
{#if opt.swatch}
|
|
495
|
+
<span
|
|
496
|
+
aria-hidden="true"
|
|
497
|
+
class="block w-3 h-3 rounded-[var(--radius-xs)] border border-[var(--border-color-default)] shadow-sm shrink-0"
|
|
498
|
+
style={`background:${opt.swatch}`}
|
|
499
|
+
></span>
|
|
500
|
+
{/if}
|
|
501
|
+
<span class="truncate">{opt.label}</span>
|
|
480
502
|
</button>
|
|
481
503
|
</li>
|
|
482
504
|
{/each}
|
package/dist/lib/Splitter.svelte
CHANGED
package/dist/lib/Table.svelte
CHANGED
|
@@ -203,7 +203,7 @@
|
|
|
203
203
|
String(col.key ?? idx);
|
|
204
204
|
</script>
|
|
205
205
|
|
|
206
|
-
<div class={wrapperClass}>
|
|
206
|
+
<div class={wrapperClass} tabindex="-1">
|
|
207
207
|
<table class={tableClass}>
|
|
208
208
|
<thead class={variantStyles.header}>
|
|
209
209
|
{#if currentVariant !== "noTitle" && currentVariant !== "list"}
|
package/dist/lib/Tabs.svelte
CHANGED
|
@@ -67,6 +67,14 @@
|
|
|
67
67
|
activeTab = tabs[0].id;
|
|
68
68
|
}
|
|
69
69
|
});
|
|
70
|
+
|
|
71
|
+
function focusActiveButton() {
|
|
72
|
+
if (!activeTab) return;
|
|
73
|
+
const btn = document.getElementById(
|
|
74
|
+
`tab-${activeTab}`
|
|
75
|
+
) as HTMLButtonElement | null;
|
|
76
|
+
btn?.focus();
|
|
77
|
+
}
|
|
70
78
|
|
|
71
79
|
function handleTabClick(tab: TabItem) {
|
|
72
80
|
if (tab.disabled) return;
|
|
@@ -95,12 +103,7 @@
|
|
|
95
103
|
const nextId = enabled[next].id;
|
|
96
104
|
activeTab = nextId;
|
|
97
105
|
onChange?.(nextId);
|
|
98
|
-
queueMicrotask(
|
|
99
|
-
const btn = document.getElementById(
|
|
100
|
-
`tab-${nextId}`
|
|
101
|
-
) as HTMLButtonElement | null;
|
|
102
|
-
btn?.focus();
|
|
103
|
-
});
|
|
106
|
+
queueMicrotask(focusActiveButton);
|
|
104
107
|
}
|
|
105
108
|
|
|
106
109
|
const sizes: Record<SizeKey, string> = {
|
|
@@ -112,7 +115,7 @@
|
|
|
112
115
|
};
|
|
113
116
|
|
|
114
117
|
const base =
|
|
115
|
-
"inline-flex items-center justify-center font-medium transition-colors duration-150 focus-visible:
|
|
118
|
+
"inline-flex items-center justify-center font-medium transition-colors duration-150 focus-visible:ring-inset focus-visible:ring-2 focus-visible:ring-[var(--border-color-focus)] focus-visible:ring-offset-0 focus:outline-none disabled:opacity-[var(--opacity-disabled)] disabled:cursor-not-allowed";
|
|
116
119
|
|
|
117
120
|
const variants = $derived({
|
|
118
121
|
default: {
|
|
@@ -160,6 +163,7 @@
|
|
|
160
163
|
aria-orientation="horizontal"
|
|
161
164
|
class={tablistClass}
|
|
162
165
|
onkeydown={handleKeyDown}
|
|
166
|
+
onfocus={focusActiveButton}
|
|
163
167
|
>
|
|
164
168
|
{#each tabs as tab (tab.id)}
|
|
165
169
|
<button
|
|
@@ -200,7 +204,7 @@
|
|
|
200
204
|
<div
|
|
201
205
|
id={`panel-${activeTab}`}
|
|
202
206
|
role="tabpanel"
|
|
203
|
-
tabindex="
|
|
207
|
+
tabindex="-1"
|
|
204
208
|
aria-labelledby={`tab-${activeTab}`}
|
|
205
209
|
class="w-full min-w-0 relative z-0 border-t border-[var(--border-color-default)] bg-[var(--color-bg-surface)] p-[var(--spacing-md)] rounded-b-[var(--radius-sm)] shadow-[0_1px_2px_0_var(--shadow-color)]"
|
|
206
210
|
>
|
package/dist/lib/index.d.ts
CHANGED
|
@@ -14,6 +14,7 @@ export { default as Hamburger } from "./Hamburger.svelte";
|
|
|
14
14
|
export { default as Menu } from "./Menu.svelte";
|
|
15
15
|
export { default as PaginatedCard } from "./PaginatedCard.svelte";
|
|
16
16
|
export { default as Pagination } from "./Pagination.svelte";
|
|
17
|
+
export { default as PrimaryColorSelect } from "./PrimaryColorSelect.svelte";
|
|
17
18
|
export { default as ProgressBar } from "./ProgressBar.svelte";
|
|
18
19
|
export { default as ProgressCircle } from "./ProgressCircle.svelte";
|
|
19
20
|
export { default as Radio } from "./Radio.svelte";
|
package/dist/lib/index.js
CHANGED
|
@@ -15,6 +15,7 @@ export { default as Hamburger } from "./Hamburger.svelte";
|
|
|
15
15
|
export { default as Menu } from "./Menu.svelte";
|
|
16
16
|
export { default as PaginatedCard } from "./PaginatedCard.svelte";
|
|
17
17
|
export { default as Pagination } from "./Pagination.svelte";
|
|
18
|
+
export { default as PrimaryColorSelect } from "./PrimaryColorSelect.svelte";
|
|
18
19
|
export { default as ProgressBar } from "./ProgressBar.svelte";
|
|
19
20
|
export { default as ProgressCircle } from "./ProgressCircle.svelte";
|
|
20
21
|
export { default as Radio } from "./Radio.svelte";
|
package/dist/lib/lang.d.ts
CHANGED
|
@@ -32,6 +32,9 @@ export declare const TEXTS: {
|
|
|
32
32
|
readonly menu: {
|
|
33
33
|
readonly subtitle: "Menu with size options";
|
|
34
34
|
};
|
|
35
|
+
readonly primaryColorSelect: {
|
|
36
|
+
readonly text: "Primary color";
|
|
37
|
+
};
|
|
35
38
|
readonly timePicker: {
|
|
36
39
|
readonly text: "Choose time";
|
|
37
40
|
readonly placeholder: "No time selected";
|
|
@@ -78,6 +81,9 @@ export declare const TEXTS: {
|
|
|
78
81
|
readonly menu: {
|
|
79
82
|
readonly subtitle: "Меню с опциями размеров";
|
|
80
83
|
};
|
|
84
|
+
readonly primaryColorSelect: {
|
|
85
|
+
readonly text: "Основной цвет";
|
|
86
|
+
};
|
|
81
87
|
readonly timePicker: {
|
|
82
88
|
readonly text: "Выбрать время";
|
|
83
89
|
readonly placeholder: "Время не выбрано";
|
|
@@ -124,6 +130,9 @@ export declare const TEXTS: {
|
|
|
124
130
|
readonly menu: {
|
|
125
131
|
readonly subtitle: "Menú con opciones de tamaño";
|
|
126
132
|
};
|
|
133
|
+
readonly primaryColorSelect: {
|
|
134
|
+
readonly text: "Color primario";
|
|
135
|
+
};
|
|
127
136
|
readonly timePicker: {
|
|
128
137
|
readonly text: "Elegir hora";
|
|
129
138
|
readonly placeholder: "Ninguna hora seleccionada";
|
package/dist/lib/lang.js
CHANGED
|
@@ -30,6 +30,7 @@ var enTexts = {
|
|
|
30
30
|
totalSize: "Total size",
|
|
31
31
|
},
|
|
32
32
|
menu: { subtitle: "Menu with size options" },
|
|
33
|
+
primaryColorSelect: { text: "Primary color" },
|
|
33
34
|
timePicker: {
|
|
34
35
|
text: "Choose time",
|
|
35
36
|
placeholder: "No time selected",
|
|
@@ -74,6 +75,7 @@ var ruTexts = {
|
|
|
74
75
|
totalSize: "Общий размер",
|
|
75
76
|
},
|
|
76
77
|
menu: { subtitle: "Меню с опциями размеров" },
|
|
78
|
+
primaryColorSelect: { text: "Основной цвет" },
|
|
77
79
|
timePicker: {
|
|
78
80
|
text: "Выбрать время",
|
|
79
81
|
placeholder: "Время не выбрано",
|
|
@@ -118,6 +120,7 @@ var esTexts = {
|
|
|
118
120
|
totalSize: "Tamaño total",
|
|
119
121
|
},
|
|
120
122
|
menu: { subtitle: "Menú con opciones de tamaño" },
|
|
123
|
+
primaryColorSelect: { text: "Color primario" },
|
|
121
124
|
timePicker: {
|
|
122
125
|
text: "Elegir hora",
|
|
123
126
|
placeholder: "Ninguna hora seleccionada",
|