@marianmeres/stuic 3.172.0 → 3.174.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 (61) hide show
  1. package/AGENTS.md +19 -4
  2. package/API.md +56 -2
  3. package/README.md +21 -5
  4. package/dist/actions/resizable-width.svelte.d.ts +14 -28
  5. package/dist/actions/resizable-width.svelte.js +16 -171
  6. package/dist/attachments/index.d.ts +1 -0
  7. package/dist/attachments/index.js +1 -0
  8. package/dist/attachments/resizable.d.ts +113 -0
  9. package/dist/attachments/resizable.fixture.svelte +53 -0
  10. package/dist/attachments/resizable.fixture.svelte.d.ts +10 -0
  11. package/dist/attachments/resizable.js +295 -0
  12. package/dist/components/Button/README.md +11 -10
  13. package/dist/components/ButtonGroupRadio/README.md +52 -23
  14. package/dist/components/ButtonGroupRadio/index.css +6 -2
  15. package/dist/components/PricingTable/PricingTable.svelte +8 -14
  16. package/dist/components/PricingTable/README.md +29 -6
  17. package/dist/components/PricingTable/index.css +54 -0
  18. package/dist/components/RangeSlider/README.md +291 -0
  19. package/dist/components/RangeSlider/RangeSlider.svelte +763 -0
  20. package/dist/components/RangeSlider/RangeSlider.svelte.d.ts +130 -0
  21. package/dist/components/RangeSlider/i18n-sk.d.ts +17 -0
  22. package/dist/components/RangeSlider/i18n-sk.js +19 -0
  23. package/dist/components/RangeSlider/i18n.d.ts +33 -0
  24. package/dist/components/RangeSlider/i18n.js +41 -0
  25. package/dist/components/RangeSlider/index.css +430 -0
  26. package/dist/components/RangeSlider/index.d.ts +3 -0
  27. package/dist/components/RangeSlider/index.js +3 -0
  28. package/dist/components/Rating/README.md +206 -0
  29. package/dist/components/Rating/Rating.svelte +355 -0
  30. package/dist/components/Rating/Rating.svelte.d.ts +82 -0
  31. package/dist/components/Rating/i18n-sk.d.ts +17 -0
  32. package/dist/components/Rating/i18n-sk.js +21 -0
  33. package/dist/components/Rating/i18n.d.ts +34 -0
  34. package/dist/components/Rating/i18n.js +42 -0
  35. package/dist/components/Rating/index.css +170 -0
  36. package/dist/components/Rating/index.d.ts +3 -0
  37. package/dist/components/Rating/index.js +3 -0
  38. package/dist/components/SplitPane/README.md +169 -0
  39. package/dist/components/SplitPane/SplitPane.svelte +202 -0
  40. package/dist/components/SplitPane/SplitPane.svelte.d.ts +67 -0
  41. package/dist/components/SplitPane/i18n-sk.d.ts +17 -0
  42. package/dist/components/SplitPane/i18n-sk.js +18 -0
  43. package/dist/components/SplitPane/i18n.d.ts +31 -0
  44. package/dist/components/SplitPane/i18n.js +39 -0
  45. package/dist/components/SplitPane/index.css +153 -0
  46. package/dist/components/SplitPane/index.d.ts +3 -0
  47. package/dist/components/SplitPane/index.js +3 -0
  48. package/dist/components/WithSidePanel/README.md +19 -16
  49. package/dist/icons/index.d.ts +2 -0
  50. package/dist/icons/index.js +3 -0
  51. package/dist/index.css +17 -0
  52. package/dist/index.d.ts +3 -0
  53. package/dist/index.js +3 -0
  54. package/docs/{maybe-todo.md → _archive/maybe-todo.md} +18 -7
  55. package/docs/architecture.md +1 -1
  56. package/docs/conventions.md +27 -7
  57. package/docs/domains/actions.md +18 -18
  58. package/docs/domains/attachments.md +72 -9
  59. package/docs/domains/components.md +175 -32
  60. package/docs/domains/theming.md +57 -13
  61. package/package.json +5 -5
@@ -0,0 +1,67 @@
1
+ import type { Snippet } from "svelte";
2
+ import type { HTMLAttributes } from "svelte/elements";
3
+ import type { ResizableInfo, ResizableUnits } from "../../attachments/resizable.js";
4
+ import type { TranslateFn } from "../../types.js";
5
+ export type SplitPaneOrientation = "horizontal" | "vertical";
6
+ /** Which pane carries the size; the other one flexes into the rest. */
7
+ export type SplitPanePrimary = "start" | "end";
8
+ export interface Props extends Omit<HTMLAttributes<HTMLDivElement>, "children"> {
9
+ /** Content of the first pane (left; top when vertical) */
10
+ start?: Snippet;
11
+ /** Content of the second pane (right; bottom when vertical) */
12
+ end?: Snippet;
13
+ /**
14
+ * `horizontal` (default) puts the panes side by side and resizes a width,
15
+ * `vertical` stacks them and resizes a height — the container then needs a
16
+ * definite height (`%` units resolve against it).
17
+ */
18
+ orientation?: SplitPaneOrientation;
19
+ /** Which pane the size applies to (the other one fills the rest). Default `start`. */
20
+ primary?: SplitPanePrimary;
21
+ /**
22
+ * Size of the primary pane in `units` (bindable). Default `50`. Written on every
23
+ * resize; writing it resizes the pane (clamped to `min` / `max`). A size stored
24
+ * under `key` wins over it on mount and is written back into the binding. The
25
+ * size the component mounted with is what double-click / Enter on the separator
26
+ * and `reset()` restore.
27
+ */
28
+ size?: number;
29
+ /** `%` (default) of the container, or `px`. Not meant to change after mount. */
30
+ units?: ResizableUnits;
31
+ /** Lower bound in `units`. `0` = none. */
32
+ min?: number;
33
+ /** Upper bound in `units`. `0` = none (`%` is still capped at 100). */
34
+ max?: number;
35
+ /** Keyboard step in `units` (Shift multiplies by 10). Default `1` for %, `10` for px. */
36
+ step?: number;
37
+ /** Persist the size under this key (`resizable-width-<key>` / `resizable-height-<key>`) */
38
+ key?: string | number | null;
39
+ /** Where `key` persists to. Default `session`. */
40
+ storage?: "local" | "session";
41
+ /** Renders the separator inert: no drag, no keyboard, not focusable */
42
+ disabled?: boolean;
43
+ /** Accessible name of the separator. Defaults to `t("resize")` — "Resize". */
44
+ label?: string;
45
+ /** Skip all default styling, use only custom classes */
46
+ unstyled?: boolean;
47
+ class?: string;
48
+ /** Classes for the first pane */
49
+ startClass?: string;
50
+ /** Classes for the second pane */
51
+ endClass?: string;
52
+ /** Classes for the separator */
53
+ separatorClass?: string;
54
+ /** Bindable root element reference */
55
+ el?: HTMLDivElement;
56
+ /** Bindable separator element reference */
57
+ separatorEl?: HTMLDivElement;
58
+ /** Fires whenever a size is applied: on mount, while dragging, per key press, on reset, on `size` writes */
59
+ onResize?: (info: ResizableInfo) => void;
60
+ /** Translation function for the separator's default name (`resize`). English built in. */
61
+ t?: TranslateFn;
62
+ }
63
+ declare const SplitPane: import("svelte").Component<Props, {
64
+ reset: () => void;
65
+ }, "el" | "size" | "separatorEl">;
66
+ type SplitPane = ReturnType<typeof SplitPane>;
67
+ export default SplitPane;
@@ -0,0 +1,17 @@
1
+ import type { SplitPaneMessages } from "./i18n.js";
2
+ /**
3
+ * Slovak message catalog for `SplitPane`. Opt-in — English stays the built-in
4
+ * default, and this module is only pulled into a bundle when it is actually
5
+ * imported (the component itself never references it).
6
+ *
7
+ * @example
8
+ * ```svelte
9
+ * <script>
10
+ * import { SplitPane, createSplitPaneT, SPLIT_PANE_MESSAGES_SK } from "@marianmeres/stuic";
11
+ * const t = createSplitPaneT(SPLIT_PANE_MESSAGES_SK);
12
+ * </script>
13
+ *
14
+ * <SplitPane {t}>…</SplitPane>
15
+ * ```
16
+ */
17
+ export declare const SPLIT_PANE_MESSAGES_SK: SplitPaneMessages;
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Slovak message catalog for `SplitPane`. Opt-in — English stays the built-in
3
+ * default, and this module is only pulled into a bundle when it is actually
4
+ * imported (the component itself never references it).
5
+ *
6
+ * @example
7
+ * ```svelte
8
+ * <script>
9
+ * import { SplitPane, createSplitPaneT, SPLIT_PANE_MESSAGES_SK } from "@marianmeres/stuic";
10
+ * const t = createSplitPaneT(SPLIT_PANE_MESSAGES_SK);
11
+ * </script>
12
+ *
13
+ * <SplitPane {t}>…</SplitPane>
14
+ * ```
15
+ */
16
+ export const SPLIT_PANE_MESSAGES_SK = {
17
+ resize: "Zmeniť veľkosť",
18
+ };
@@ -0,0 +1,31 @@
1
+ import type { TranslateFn } from "../../types.js";
2
+ /**
3
+ * The built-in (English) message catalog of `SplitPane`. Also the fallback of
4
+ * every other bundled locale, so a locale missing a key still renders text.
5
+ */
6
+ export declare const SPLIT_PANE_MESSAGES_EN: {
7
+ /** Default accessible name of the separator (the drag handle) */
8
+ resize: string;
9
+ };
10
+ /** Every message key `SplitPane` may look up. */
11
+ export type SplitPaneMessageKey = keyof typeof SPLIT_PANE_MESSAGES_EN;
12
+ /** A (possibly partial) catalog for one locale. */
13
+ export type SplitPaneMessages = Record<SplitPaneMessageKey, string>;
14
+ /**
15
+ * Builds the `t` prop of `SplitPane` from a message catalog. Unknown or
16
+ * untranslated keys fall back to `fallbackMessages` (English by default), so a
17
+ * catalog may safely be partial and never renders a raw key.
18
+ *
19
+ * @example
20
+ * ```svelte
21
+ * <script>
22
+ * import { SplitPane, createSplitPaneT, SPLIT_PANE_MESSAGES_SK } from "@marianmeres/stuic";
23
+ * const t = createSplitPaneT(SPLIT_PANE_MESSAGES_SK);
24
+ * </script>
25
+ *
26
+ * <SplitPane {t}>…</SplitPane>
27
+ * ```
28
+ */
29
+ export declare function createSplitPaneT(messages: Partial<SplitPaneMessages> | Record<string, string>, fallbackMessages?: Partial<SplitPaneMessages> | Record<string, string>): TranslateFn;
30
+ /** The component's built-in English `t`. */
31
+ export declare const t_default: TranslateFn;
@@ -0,0 +1,39 @@
1
+ import { isPlainObject } from "../../utils/is-plain-object.js";
2
+ import { replaceMap } from "../../utils/replace-map.js";
3
+ /**
4
+ * The built-in (English) message catalog of `SplitPane`. Also the fallback of
5
+ * every other bundled locale, so a locale missing a key still renders text.
6
+ */
7
+ export const SPLIT_PANE_MESSAGES_EN = {
8
+ /** Default accessible name of the separator (the drag handle) */
9
+ resize: "Resize",
10
+ };
11
+ /**
12
+ * Builds the `t` prop of `SplitPane` from a message catalog. Unknown or
13
+ * untranslated keys fall back to `fallbackMessages` (English by default), so a
14
+ * catalog may safely be partial and never renders a raw key.
15
+ *
16
+ * @example
17
+ * ```svelte
18
+ * <script>
19
+ * import { SplitPane, createSplitPaneT, SPLIT_PANE_MESSAGES_SK } from "@marianmeres/stuic";
20
+ * const t = createSplitPaneT(SPLIT_PANE_MESSAGES_SK);
21
+ * </script>
22
+ *
23
+ * <SplitPane {t}>…</SplitPane>
24
+ * ```
25
+ */
26
+ export function createSplitPaneT(messages, fallbackMessages = SPLIT_PANE_MESSAGES_EN) {
27
+ return (k, values = null, fallback = "") => {
28
+ const out = messages[k] ??
29
+ fallbackMessages[k] ??
30
+ (typeof fallback === "string" ? fallback : k);
31
+ return isPlainObject(values)
32
+ ? replaceMap(out, values, {
33
+ preSearchKeyTransform: (k) => `{${k}}`,
34
+ })
35
+ : out;
36
+ };
37
+ }
38
+ /** The component's built-in English `t`. */
39
+ export const t_default = createSplitPaneT(SPLIT_PANE_MESSAGES_EN);
@@ -0,0 +1,153 @@
1
+ /* ============================================================================
2
+ SPLIT PANE COMPONENT TOKENS
3
+ Override globally: :root { --stuic-split-pane-separator-color: red; }
4
+ Override locally: <SplitPane style="--stuic-split-pane-separator-color: red;" />
5
+
6
+ The defaults reproduce the `resizable` attachment's built-in handle (the one
7
+ `resizableWidth` / `WithSidePanel` show): a faint 1px line with a small rounded grip.
8
+ ============================================================================ */
9
+
10
+ :root {
11
+ /* the separator line */
12
+ --stuic-split-pane-separator-color: rgb(0 0 0 / 0.2);
13
+ /* … while hovered, focused or dragged */
14
+ --stuic-split-pane-separator-color-hover: rgb(0 0 0 / 0.3);
15
+ --stuic-split-pane-separator-thickness: 1px;
16
+ /* invisible grab zone added on each side of the line */
17
+ --stuic-split-pane-separator-hit-area: 4px;
18
+ /* the centered grip on the line */
19
+ --stuic-split-pane-grip-color: var(--color-gray-300);
20
+ --stuic-split-pane-grip-color-hover: var(--color-gray-400);
21
+ --stuic-split-pane-grip-border-color: rgb(0 0 0 / 0.2);
22
+ --stuic-split-pane-grip-length: 20px;
23
+ --stuic-split-pane-grip-thickness: 9px;
24
+ --stuic-split-pane-grip-radius: 0.25rem;
25
+ --stuic-split-pane-ring-color: var(--stuic-color-ring);
26
+ }
27
+
28
+ :root.dark {
29
+ --stuic-split-pane-separator-color: rgb(255 255 255 / 0.1);
30
+ --stuic-split-pane-separator-color-hover: rgb(255 255 255 / 0.2);
31
+ --stuic-split-pane-grip-color: var(--color-gray-600);
32
+ --stuic-split-pane-grip-color-hover: var(--color-gray-500);
33
+ --stuic-split-pane-grip-border-color: rgb(255 255 255 / 0.2);
34
+ }
35
+
36
+ @layer components {
37
+ /* fills its container (a definite container height is what makes the panes scroll
38
+ instead of growing — and what `%` resolves against when vertical) */
39
+ .stuic-split-pane {
40
+ display: flex;
41
+ width: 100%;
42
+ height: 100%;
43
+ overflow: hidden;
44
+ }
45
+
46
+ .stuic-split-pane[data-orientation="horizontal"] {
47
+ flex-direction: row;
48
+ }
49
+
50
+ .stuic-split-pane[data-orientation="vertical"] {
51
+ flex-direction: column;
52
+ }
53
+
54
+ /* the non-primary pane fills what is left and may shrink below its content */
55
+ .stuic-split-pane-pane {
56
+ flex: 1 1 0%;
57
+ min-width: 0;
58
+ min-height: 0;
59
+ overflow: auto;
60
+ }
61
+
62
+ /* the primary pane is exactly its (inline) width / height */
63
+ .stuic-split-pane-pane[data-primary] {
64
+ flex: 0 0 auto;
65
+ }
66
+
67
+ .stuic-split-pane-separator {
68
+ position: relative;
69
+ z-index: 1;
70
+ flex: 0 0 var(--stuic-split-pane-separator-thickness);
71
+ background: var(--stuic-split-pane-separator-color);
72
+ touch-action: none;
73
+ user-select: none;
74
+ outline: none;
75
+ transition: background-color
76
+ var(--stuic-split-pane-transition, var(--stuic-transition));
77
+ }
78
+
79
+ .stuic-split-pane[data-orientation="horizontal"] > .stuic-split-pane-separator {
80
+ cursor: col-resize;
81
+ }
82
+
83
+ .stuic-split-pane[data-orientation="vertical"] > .stuic-split-pane-separator {
84
+ cursor: row-resize;
85
+ }
86
+
87
+ /* grab zone: wider than the line, invisible, paints above the panes (z-index) */
88
+ .stuic-split-pane-separator::before {
89
+ content: "";
90
+ position: absolute;
91
+ inset: 0;
92
+ }
93
+
94
+ .stuic-split-pane[data-orientation="horizontal"] > .stuic-split-pane-separator::before {
95
+ inset-inline: calc(-1 * var(--stuic-split-pane-separator-hit-area));
96
+ }
97
+
98
+ .stuic-split-pane[data-orientation="vertical"] > .stuic-split-pane-separator::before {
99
+ inset-block: calc(-1 * var(--stuic-split-pane-separator-hit-area));
100
+ }
101
+
102
+ /* grip: a small rounded tab centered on the line */
103
+ .stuic-split-pane-separator::after {
104
+ content: "";
105
+ position: absolute;
106
+ left: 50%;
107
+ top: 50%;
108
+ translate: -50% -50%;
109
+ box-sizing: border-box;
110
+ border: 1px solid var(--stuic-split-pane-grip-border-color);
111
+ border-radius: var(--stuic-split-pane-grip-radius);
112
+ background: var(--stuic-split-pane-grip-color);
113
+ transition: background-color
114
+ var(--stuic-split-pane-transition, var(--stuic-transition));
115
+ }
116
+
117
+ .stuic-split-pane[data-orientation="horizontal"] > .stuic-split-pane-separator::after {
118
+ width: var(--stuic-split-pane-grip-thickness);
119
+ height: var(--stuic-split-pane-grip-length);
120
+ }
121
+
122
+ .stuic-split-pane[data-orientation="vertical"] > .stuic-split-pane-separator::after {
123
+ width: var(--stuic-split-pane-grip-length);
124
+ height: var(--stuic-split-pane-grip-thickness);
125
+ }
126
+
127
+ .stuic-split-pane-separator:hover,
128
+ .stuic-split-pane-separator:focus-visible,
129
+ .stuic-split-pane-separator[data-resizing] {
130
+ background: var(--stuic-split-pane-separator-color-hover);
131
+ }
132
+
133
+ .stuic-split-pane-separator:hover::after,
134
+ .stuic-split-pane-separator:focus-visible::after,
135
+ .stuic-split-pane-separator[data-resizing]::after {
136
+ background: var(--stuic-split-pane-grip-color-hover);
137
+ }
138
+
139
+ .stuic-split-pane-separator:focus-visible {
140
+ outline: 2px solid var(--stuic-split-pane-ring-color);
141
+ outline-offset: 0;
142
+ }
143
+
144
+ /* inert: a plain divider */
145
+ .stuic-split-pane[data-disabled] > .stuic-split-pane-separator {
146
+ cursor: default;
147
+ background: var(--stuic-split-pane-separator-color);
148
+ }
149
+
150
+ .stuic-split-pane[data-disabled] > .stuic-split-pane-separator::after {
151
+ display: none;
152
+ }
153
+ }
@@ -0,0 +1,3 @@
1
+ export { default as SplitPane, type Props as SplitPaneProps, type SplitPaneOrientation, type SplitPanePrimary, } from "./SplitPane.svelte";
2
+ export { createSplitPaneT, SPLIT_PANE_MESSAGES_EN, type SplitPaneMessageKey, type SplitPaneMessages, } from "./i18n.js";
3
+ export { SPLIT_PANE_MESSAGES_SK } from "./i18n-sk.js";
@@ -0,0 +1,3 @@
1
+ export { default as SplitPane, } from "./SplitPane.svelte";
2
+ export { createSplitPaneT, SPLIT_PANE_MESSAGES_EN, } from "./i18n.js";
3
+ export { SPLIT_PANE_MESSAGES_SK } from "./i18n-sk.js";
@@ -7,22 +7,22 @@ auto-hides when the container width falls below a configurable threshold
7
7
 
8
8
  ## Props
9
9
 
10
- | Prop | Type | Default | Description |
11
- | -------------------- | ------------------------------------------- | --------- | ------------------------------------------------ |
12
- | `children` | `Snippet` | — | Main content |
13
- | `side` | `Snippet` | — | Side panel content |
14
- | `position` | `"left" \| "right"` | `"left"` | Side panel position |
15
- | `width` | `string` | `"300px"` | Side panel width (CSS value, px or %) |
16
- | `threshold` | `number` | `768` | Container width (px) below which side auto-hides |
17
- | `transition` | `boolean` | `true` | Enable Svelte slide transition |
18
- | `transitionDuration` | `number` | `200` | Transition duration in ms |
19
- | `open` | `boolean` | `true` | Desktop visibility state (bindable) |
20
- | `resizable` | `boolean \| Partial<ResizableWidthOptions>` | `false` | Enable drag-resizable side panel |
21
- | `classMain` | `string` | — | Custom class for main content div |
22
- | `classSide` | `string` | — | Custom class for side panel div |
23
- | `class` | `string` | — | Custom class for wrapper div |
24
- | `unstyled` | `boolean` | `false` | Skip all default styling |
25
- | `el` | `HTMLDivElement` | — | Bindable wrapper element reference |
10
+ | Prop | Type | Default | Description |
11
+ | -------------------- | ------------------------------------------- | --------- | ---------------------------------------------------------------------------------------- |
12
+ | `children` | `Snippet` | — | Main content |
13
+ | `side` | `Snippet` | — | Side panel content |
14
+ | `position` | `"left" \| "right"` | `"left"` | Side panel position |
15
+ | `width` | `string` | `"300px"` | Side panel width (CSS value, px or %) |
16
+ | `threshold` | `number` | `768` | Container width (px) below which side auto-hides |
17
+ | `transition` | `boolean` | `true` | Enable Svelte slide transition |
18
+ | `transitionDuration` | `number` | `200` | Transition duration in ms |
19
+ | `open` | `boolean` | `true` | Desktop visibility state (bindable) |
20
+ | `resizable` | `boolean \| Partial<ResizableWidthOptions>` | `false` | Enable drag-resizable side panel (the edge handle is also a keyboard-operable separator) |
21
+ | `classMain` | `string` | — | Custom class for main content div |
22
+ | `classSide` | `string` | — | Custom class for side panel div |
23
+ | `class` | `string` | — | Custom class for wrapper div |
24
+ | `unstyled` | `boolean` | `false` | Skip all default styling |
25
+ | `el` | `HTMLDivElement` | — | Bindable wrapper element reference |
26
26
 
27
27
  ## API (via `bind:this`)
28
28
 
@@ -67,6 +67,9 @@ auto-hides when the container width falls below a configurable threshold
67
67
 
68
68
  ### Resizable
69
69
 
70
+ The edge handle is a focusable `role="separator"` (the `resizable` attachment's): arrow keys
71
+ resize it (Shift ×10), Home / End go to `min` / `max`, Enter or double-click resets.
72
+
70
73
  ```svelte
71
74
  <WithSidePanel resizable={{ min: 200, max: 500, key: "sidebar", storage: "local" }}>
72
75
  {#snippet side()}
@@ -58,6 +58,8 @@ export { iconLucideSun as iconSun } from "@marianmeres/icons-fns/lucide/iconLuci
58
58
  export { iconLucideUser as iconUser } from "@marianmeres/icons-fns/lucide/iconLucideUser.js";
59
59
  export { iconLucideX as iconX } from "@marianmeres/icons-fns/lucide/iconLucideX.js";
60
60
  export { iconLucidePencil as iconPencil } from "@marianmeres/icons-fns/lucide/iconLucidePencil.js";
61
+ export { iconBsStar as iconStar } from "@marianmeres/icons-fns/bootstrap/iconBsStar.js";
62
+ export { iconBsStarFill as iconStarFill } from "@marianmeres/icons-fns/bootstrap/iconBsStarFill.js";
61
63
  export { iconLucideBold as iconBold } from "@marianmeres/icons-fns/lucide/iconLucideBold.js";
62
64
  export { iconLucideItalic as iconItalic } from "@marianmeres/icons-fns/lucide/iconLucideItalic.js";
63
65
  export { iconLucideHeading1 as iconHeading1 } from "@marianmeres/icons-fns/lucide/iconLucideHeading1.js";
@@ -63,6 +63,9 @@ export { iconLucideSun as iconSun } from "@marianmeres/icons-fns/lucide/iconLuci
63
63
  export { iconLucideUser as iconUser } from "@marianmeres/icons-fns/lucide/iconLucideUser.js";
64
64
  export { iconLucideX as iconX } from "@marianmeres/icons-fns/lucide/iconLucideX.js";
65
65
  export { iconLucidePencil as iconPencil } from "@marianmeres/icons-fns/lucide/iconLucidePencil.js";
66
+ // Rating Icons (Bootstrap)
67
+ export { iconBsStar as iconStar } from "@marianmeres/icons-fns/bootstrap/iconBsStar.js";
68
+ export { iconBsStarFill as iconStarFill } from "@marianmeres/icons-fns/bootstrap/iconBsStarFill.js";
66
69
  // Markdown Editor / formatting toolbar Icons (Lucide)
67
70
  // (bullet list reuses `iconList` and horizontal rule reuses `iconMinus` above)
68
71
  export { iconLucideBold as iconBold } from "@marianmeres/icons-fns/lucide/iconLucideBold.js";
package/dist/index.css CHANGED
@@ -29,6 +29,20 @@ In practice:
29
29
  These tokens provide a single point of control for cross-component properties.
30
30
  Override globally: :root { --stuic-radius: 0; }
31
31
  Override per-component: --stuic-button-radius: 9999px; (takes precedence)
32
+
33
+ NOTE on the mirrored names. The tokens below are TIER tokens, named
34
+ --stuic-{property}-{tier}, and they ARE declared here. Component override tokens
35
+ are the transposition, --stuic-{component}-{property}, and are NEVER declared --
36
+ they exist only as the first argument of a var() fallback at the usage site:
37
+
38
+ border-radius: var(--stuic-button-radius, var(--stuic-radius-button));
39
+ ^ component token (3) ^ tier token (2, declared here)
40
+
41
+ So --stuic-radius-button (tier) applies to Button, SplitButton and ButtonGroupRadio,
42
+ while --stuic-button-radius (component) applies to Button alone. Same relationship
43
+ between --stuic-border-width-button and --stuic-button-border-width. "button" is the
44
+ only tier name that is also a component name, hence the only confusable pair -- do
45
+ not "fix" one into the other.
32
46
  ============================================================================ */
33
47
  :root {
34
48
  /* Radius: three tiers */
@@ -103,11 +117,14 @@ In practice:
103
117
  @import "./components/Pill/index.css";
104
118
  @import "./components/PricingTable/index.css";
105
119
  @import "./components/Progress/index.css";
120
+ @import "./components/RangeSlider/index.css";
121
+ @import "./components/Rating/index.css";
106
122
  @import "./components/Separator/index.css";
107
123
  @import "./components/Skeleton/index.css";
108
124
  @import "./components/Slider/index.css";
109
125
  @import "./components/Spinner/index.css";
110
126
  @import "./components/SplitButton/index.css";
127
+ @import "./components/SplitPane/index.css";
111
128
  @import "./components/Stat/index.css";
112
129
  @import "./components/Stepper/index.css";
113
130
  @import "./components/Switch/index.css";
package/dist/index.d.ts CHANGED
@@ -73,12 +73,15 @@ export * from "./components/Pagination/index.js";
73
73
  export * from "./components/Pill/index.js";
74
74
  export * from "./components/PricingTable/index.js";
75
75
  export * from "./components/Progress/index.js";
76
+ export * from "./components/RangeSlider/index.js";
77
+ export * from "./components/Rating/index.js";
76
78
  export * from "./components/Separator/index.js";
77
79
  export * from "./components/Skeleton/index.js";
78
80
  export * from "./components/Slider/index.js";
79
81
  export * from "./components/SlidingPanels/index.js";
80
82
  export * from "./components/Spinner/index.js";
81
83
  export * from "./components/SplitButton/index.js";
84
+ export * from "./components/SplitPane/index.js";
82
85
  export * from "./components/Stat/index.js";
83
86
  export * from "./components/Stepper/index.js";
84
87
  export * from "./components/Switch/index.js";
package/dist/index.js CHANGED
@@ -79,12 +79,15 @@ export * from "./components/Pagination/index.js";
79
79
  export * from "./components/Pill/index.js";
80
80
  export * from "./components/PricingTable/index.js";
81
81
  export * from "./components/Progress/index.js";
82
+ export * from "./components/RangeSlider/index.js";
83
+ export * from "./components/Rating/index.js";
82
84
  export * from "./components/Separator/index.js";
83
85
  export * from "./components/Skeleton/index.js";
84
86
  export * from "./components/Slider/index.js";
85
87
  export * from "./components/SlidingPanels/index.js";
86
88
  export * from "./components/Spinner/index.js";
87
89
  export * from "./components/SplitButton/index.js";
90
+ export * from "./components/SplitPane/index.js";
88
91
  export * from "./components/Stat/index.js";
89
92
  export * from "./components/Stepper/index.js";
90
93
  export * from "./components/Switch/index.js";
@@ -49,16 +49,27 @@ Checked first, to avoid false positives:
49
49
  reusable `longPress` attachment), Shift+F10 / menu key.
50
50
  6. ~~**EmptyState**~~ — ✅ shipped (see `src/lib/components/EmptyState/`): icon +
51
51
  title + description + CTA placeholder for empty lists/tables/search results.
52
- 7. **Rating (stars)** — display + input variants. `FieldLikeButton` is adjacent but not
53
- this.
52
+ 7. ~~**Rating (stars)**~~ — ✅ shipped (see `src/lib/components/Rating/`): input
53
+ (radiogroup of per-star radios, half steps, hover preview, click-again-to-clear,
54
+ keyboard, hidden input + `validate` with `required`) and `readonly` display
55
+ (`role="img"`, fractional fill); custom `icon` / `iconEmpty`, `intent`, sizes,
56
+ `t` texts with Slovak bundled.
54
57
 
55
58
  ## Tier 3 — common, but judge by actual app needs
56
59
 
57
- 8. **SplitPane / resizable panels** — general two-pane draggable splitter
58
- (horizontal/vertical, min/max, persisted size). `actions/resizable-width`,
59
- `WithSidePanel`, and `persistent-state` already provide the pieces.
60
- 9. **Range slider (dual-thumb)** — `Slider` is deliberately single-value; a min/max
61
- range variant (price filters etc.) is a recurring ask.
60
+ 8. ~~**SplitPane / resizable panels**~~ — ✅ shipped (see `src/lib/components/SplitPane/`
61
+ and `src/lib/attachments/resizable.ts`): the `resizable-width` action generalised into a
62
+ `resizable` attachment (`axis: "x" | "y"`, `px` / `%`, `min` / `max`, `key` persistence,
63
+ `handle` for an own separator, `onInit` api) whose handle is an ARIA window splitter
64
+ (arrows / Home / End / Enter); `resizableWidth` stays as a thin wrapper over it, so
65
+ `WithSidePanel` got the keyboard for free. `SplitPane` wraps it: `start` / `end`
66
+ snippets, `orientation`, `primary`, `bind:size`, `reset()`, `disabled`, tokens, `t` with
67
+ Slovak bundled.
68
+ 9. ~~**Range slider (dual-thumb)**~~ — ✅ shipped (see `src/lib/components/RangeSlider/`):
69
+ `Slider`'s two-value sibling — `bind:start` / `bind:end` on one track with the fill
70
+ between them; nearest-thumb press, grab-to-drag, thumbs never cross (`minRange`),
71
+ per-thumb keyboard via two hidden range inputs (`nameStart` / `nameEnd`), ticks,
72
+ value labels, `validate` with the pair, `t` thumb names with Slovak bundled.
62
73
  10. ~~**Timeline / activity feed**~~ — ✅ shipped (see `src/lib/components/Timeline/`):
63
74
  vertical event list on a rail with dot / icon-bubble / custom (snippet) markers,
64
75
  per-item intent, `<time datetime>` support with an optional formatter, inline or
@@ -36,7 +36,7 @@ Layer 4: Internal Vars (--_bg, --_text, --_border)
36
36
 
37
37
  ```
38
38
  src/lib/
39
- ├── components/ # 75 component directories
39
+ ├── components/ # 78 component directories
40
40
  │ └── {Name}/
41
41
  │ ├── {Name}.svelte # Main component
42
42
  │ ├── index.ts # Exports
@@ -43,12 +43,14 @@ Global tokens in `src/lib/index.css` that control cross-component visual propert
43
43
  /* Brutalist example — 7 lines to transform everything */
44
44
  :root {
45
45
  --stuic-radius: 0;
46
+ --stuic-radius-button: 0;
46
47
  --stuic-radius-container: 0;
47
48
  --stuic-shadow: none;
48
49
  --stuic-shadow-hover: none;
49
50
  --stuic-shadow-overlay: none;
50
51
  --stuic-shadow-dialog: none;
51
52
  --stuic-border-width: 0;
53
+ --stuic-border-width-button: 0;
52
54
  }
53
55
  ```
54
56
 
@@ -79,16 +81,34 @@ Component CSS must reference shared tokens as **fallbacks at usage sites**, not
79
81
 
80
82
  Per-component overrides (`--stuic-widget-radius: 0`) still take precedence over the shared fallback.
81
83
 
82
- ### Element vs Container
84
+ ### Element vs Button vs Container
83
85
 
84
- Two tiers of radius for natural visual hierarchy:
86
+ Three tiers of radius for natural visual hierarchy:
85
87
 
86
- | Tier | Token | Default | Use for |
87
- | --------- | -------------------------- | ------------------ | ----------------------------------------------------- |
88
- | Element | `--stuic-radius` | `var(--radius-md)` | Buttons, inputs, badges, list items, checkboxes, tabs |
89
- | Container | `--stuic-radius-container` | `var(--radius-lg)` | Cards, modals, dropdowns, notifications, accordions |
88
+ | Tier | Token | Default | Use for |
89
+ | --------- | -------------------------- | ------------------ | --------------------------------------------------- |
90
+ | Element | `--stuic-radius` | `var(--radius-md)` | Inputs, badges, list items, checkboxes, tabs |
91
+ | Button | `--stuic-radius-button` | `var(--radius-md)` | Buttons, split buttons, button groups |
92
+ | Container | `--stuic-radius-container` | `var(--radius-lg)` | Cards, modals, dropdowns, notifications, accordions |
90
93
 
91
- **Rule of thumb:** if it wraps other interactive elements, it's a container.
94
+ **Rule of thumb:** if it wraps other interactive elements, it's a container. Buttons get
95
+ their own tier so a flat-input theme can still have pill buttons (and vice versa);
96
+ `--stuic-border-width-button` splits out the same way.
97
+
98
+ ### Tier tokens vs component tokens
99
+
100
+ The two layers use mirrored names, which is easy to misread for the button tier:
101
+
102
+ | Name | Layer | Declared? | Scope |
103
+ | ----------------------------- | ------------------- | --------------------- | ----------------------------- |
104
+ | `--stuic-radius-button` | shared tier (2) | yes, in `index.css` | every button-family component |
105
+ | `--stuic-button-radius` | component token (3) | no, fallback arg only | `Button` only |
106
+ | `--stuic-border-width-button` | shared tier (2) | yes, in `index.css` | every button-family component |
107
+ | `--stuic-button-border-width` | component token (3) | no, fallback arg only | `Button` only |
108
+
109
+ Read it as `--stuic-{property}-{tier}` for tier tokens and `--stuic-{component}-{property}`
110
+ for component tokens. `button` is the only tier name that is also a component name, so it
111
+ is the only pair that reads as a transposition.
92
112
 
93
113
  ---
94
114
 
@@ -8,24 +8,24 @@
8
8
 
9
9
  ## Available Actions
10
10
 
11
- | Action | Purpose | File |
12
- | ------------------------- | ------------------------------------------------------------- | ------------------------------------ |
13
- | `validate` | Form field validation with i18n support | `validate.svelte.ts` |
14
- | `focusTrap` | Keyboard focus containment (modals/dialogs) | `focus-trap.ts` |
15
- | `autogrow` | Auto-resize textarea to content | `autogrow.svelte.ts` |
16
- | `autoscroll` | Auto-scroll container to bottom | `autoscroll.ts` |
17
- | `dimBehind` | Dim everything behind a target element (simplified spotlight) | `dim-behind/` |
18
- | `fileDropzone` | Drag-and-drop file handling | `file-dropzone.svelte.ts` |
19
- | `highlightDragover` | Visual feedback on drag-over | `highlight-dragover.svelte.ts` |
20
- | `resizableWidth` | Draggable width resizing | `resizable-width.svelte.ts` |
21
- | `draggable` | Pointer-delta drag handle (consumer owns the movement) | `draggable.svelte.ts` |
22
- | `trim` | Auto-trim whitespace from input | `trim.svelte.ts` |
23
- | `typeahead` | Advanced autocomplete behavior | `typeahead.svelte.ts` |
24
- | `onSubmitValidityCheck` | Form submit validation | `on-submit-validity-check.svelte.ts` |
25
- | `popover` | Popover positioning | `popover/` |
26
- | `spotlight` | Spotlight/coach mark overlay with cutout hole | `spotlight/` |
27
- | `tooltip` | Tooltip positioning and display | `tooltip/` |
28
- | `createTour` / `tourStep` | Multi-step onboarding tour (built on spotlight) | `onboarding/` |
11
+ | Action | Purpose | File |
12
+ | ------------------------- | ----------------------------------------------------------------------- | ------------------------------------ |
13
+ | `validate` | Form field validation with i18n support | `validate.svelte.ts` |
14
+ | `focusTrap` | Keyboard focus containment (modals/dialogs) | `focus-trap.ts` |
15
+ | `autogrow` | Auto-resize textarea to content | `autogrow.svelte.ts` |
16
+ | `autoscroll` | Auto-scroll container to bottom | `autoscroll.ts` |
17
+ | `dimBehind` | Dim everything behind a target element (simplified spotlight) | `dim-behind/` |
18
+ | `fileDropzone` | Drag-and-drop file handling | `file-dropzone.svelte.ts` |
19
+ | `highlightDragover` | Visual feedback on drag-over | `highlight-dragover.svelte.ts` |
20
+ | `resizableWidth` | Draggable width resizing (thin wrapper over the `resizable` attachment) | `resizable-width.svelte.ts` |
21
+ | `draggable` | Pointer-delta drag handle (consumer owns the movement) | `draggable.svelte.ts` |
22
+ | `trim` | Auto-trim whitespace from input | `trim.svelte.ts` |
23
+ | `typeahead` | Advanced autocomplete behavior | `typeahead.svelte.ts` |
24
+ | `onSubmitValidityCheck` | Form submit validation | `on-submit-validity-check.svelte.ts` |
25
+ | `popover` | Popover positioning | `popover/` |
26
+ | `spotlight` | Spotlight/coach mark overlay with cutout hole | `spotlight/` |
27
+ | `tooltip` | Tooltip positioning and display | `tooltip/` |
28
+ | `createTour` / `tourStep` | Multi-step onboarding tour (built on spotlight) | `onboarding/` |
29
29
 
30
30
  ---
31
31