@marianmeres/stuic 3.171.0 → 3.173.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/AGENTS.md +10 -8
- package/API.md +146 -2
- package/README.md +15 -3
- package/dist/actions/resizable-width.svelte.d.ts +14 -28
- package/dist/actions/resizable-width.svelte.js +16 -171
- package/dist/attachments/index.d.ts +1 -0
- package/dist/attachments/index.js +1 -0
- package/dist/attachments/resizable.d.ts +113 -0
- package/dist/attachments/resizable.fixture.svelte +53 -0
- package/dist/attachments/resizable.fixture.svelte.d.ts +10 -0
- package/dist/attachments/resizable.js +295 -0
- package/dist/components/AssetsPreview/AssetsPreview.fixture.svelte +1 -1
- package/dist/components/Calendar/Calendar.svelte +799 -0
- package/dist/components/Calendar/Calendar.svelte.d.ts +135 -0
- package/dist/components/Calendar/README.md +294 -0
- package/dist/components/Calendar/calendar-i18n-sk.d.ts +20 -0
- package/dist/components/Calendar/calendar-i18n-sk.js +46 -0
- package/dist/components/Calendar/calendar-i18n.d.ts +61 -0
- package/dist/components/Calendar/calendar-i18n.js +73 -0
- package/dist/components/Calendar/index.css +307 -0
- package/dist/components/Calendar/index.d.ts +5 -0
- package/dist/components/Calendar/index.js +5 -0
- package/dist/components/Calendar/iso-date.d.ts +107 -0
- package/dist/components/Calendar/iso-date.js +247 -0
- package/dist/components/CommandMenu/CommandMenu.fixture.svelte +1 -1
- package/dist/components/Input/FieldDate.svelte +349 -0
- package/dist/components/Input/FieldDate.svelte.d.ts +79 -0
- package/dist/components/Input/FieldDateRange.svelte +373 -0
- package/dist/components/Input/FieldDateRange.svelte.d.ts +90 -0
- package/dist/components/Input/README.md +149 -16
- package/dist/components/Input/_internal/FieldDateShell.svelte +327 -0
- package/dist/components/Input/_internal/FieldDateShell.svelte.d.ts +66 -0
- package/dist/components/Input/index.css +119 -0
- package/dist/components/Input/index.d.ts +2 -0
- package/dist/components/Input/index.js +2 -0
- package/dist/components/ModalDialog/ModalDialog.fixture.svelte +1 -1
- package/dist/components/RangeSlider/README.md +291 -0
- package/dist/components/RangeSlider/RangeSlider.svelte +763 -0
- package/dist/components/RangeSlider/RangeSlider.svelte.d.ts +130 -0
- package/dist/components/RangeSlider/i18n-sk.d.ts +17 -0
- package/dist/components/RangeSlider/i18n-sk.js +19 -0
- package/dist/components/RangeSlider/i18n.d.ts +33 -0
- package/dist/components/RangeSlider/i18n.js +41 -0
- package/dist/components/RangeSlider/index.css +430 -0
- package/dist/components/RangeSlider/index.d.ts +3 -0
- package/dist/components/RangeSlider/index.js +3 -0
- package/dist/components/Rating/README.md +206 -0
- package/dist/components/Rating/Rating.svelte +355 -0
- package/dist/components/Rating/Rating.svelte.d.ts +82 -0
- package/dist/components/Rating/i18n-sk.d.ts +17 -0
- package/dist/components/Rating/i18n-sk.js +21 -0
- package/dist/components/Rating/i18n.d.ts +34 -0
- package/dist/components/Rating/i18n.js +42 -0
- package/dist/components/Rating/index.css +170 -0
- package/dist/components/Rating/index.d.ts +3 -0
- package/dist/components/Rating/index.js +3 -0
- package/dist/components/SlidingPanels/SlidingPanels.fixture.svelte +1 -1
- package/dist/components/SplitPane/README.md +169 -0
- package/dist/components/SplitPane/SplitPane.svelte +202 -0
- package/dist/components/SplitPane/SplitPane.svelte.d.ts +67 -0
- package/dist/components/SplitPane/i18n-sk.d.ts +17 -0
- package/dist/components/SplitPane/i18n-sk.js +18 -0
- package/dist/components/SplitPane/i18n.d.ts +31 -0
- package/dist/components/SplitPane/i18n.js +39 -0
- package/dist/components/SplitPane/index.css +153 -0
- package/dist/components/SplitPane/index.d.ts +3 -0
- package/dist/components/SplitPane/index.js +3 -0
- package/dist/components/WithSidePanel/README.md +19 -16
- package/dist/icons/index.d.ts +3 -0
- package/dist/icons/index.js +4 -0
- package/dist/index.css +4 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +4 -0
- package/docs/_archive/README.md +10 -0
- package/docs/{component-testing → _archive/component-testing}/00-overview-and-roadmap.md +8 -6
- package/docs/{component-testing → _archive/component-testing}/01-framework-setup.md +4 -2
- package/docs/{component-testing → _archive/component-testing}/03-component-coverage-roadmap.md +3 -1
- package/docs/{component-testing → _archive/component-testing}/04-hard-cases-and-e2e.md +5 -3
- package/docs/{component-testing → _archive/component-testing}/05-ci.md +2 -0
- package/docs/{component-testing → _archive/component-testing}/PROGRESS.md +3 -1
- package/docs/_archive/component-testing/README.md +28 -0
- package/docs/{maybe-todo.md → _archive/maybe-todo.md} +24 -13
- package/docs/{upgrading.md → _archive/upgrading.md} +2 -0
- package/docs/architecture.md +19 -11
- package/docs/domains/actions.md +21 -20
- package/docs/domains/attachments.md +72 -9
- package/docs/domains/components.md +195 -49
- package/docs/domains/utils.md +2 -1
- package/docs/tasks.md +19 -9
- package/docs/{component-testing/02-test-conventions.md → testing-components.md} +30 -31
- package/docs/testing.md +3 -3
- package/package.json +6 -5
- package/docs/component-testing/README.md +0 -38
package/AGENTS.md
CHANGED
|
@@ -23,10 +23,10 @@
|
|
|
23
23
|
|
|
24
24
|
```
|
|
25
25
|
src/lib/
|
|
26
|
-
├── components/ #
|
|
27
|
-
├── actions/ #
|
|
26
|
+
├── components/ # 78 component directories
|
|
27
|
+
├── actions/ # 16 Svelte actions (use: directives)
|
|
28
28
|
├── attachments/ # Svelte attachments ({@attach} — preferred for new DOM helpers)
|
|
29
|
-
├── utils/ #
|
|
29
|
+
├── utils/ # 55 utility modules (48 on the barrel)
|
|
30
30
|
├── icons/ # Icon re-exports from @marianmeres/icons-fns
|
|
31
31
|
├── css/ # CSS-only presets (ratio-locked frame / letterbox)
|
|
32
32
|
├── index.css # Centralized CSS imports
|
|
@@ -39,7 +39,7 @@ src/lib/
|
|
|
39
39
|
> attachments in `src/lib/attachments/` (export from its `index.ts`). The existing `actions/`
|
|
40
40
|
> are kept as-is for back-compat; no need to migrate them.
|
|
41
41
|
|
|
42
|
-
Theme CSS files are not bundled in this package — they're provided by `@marianmeres/design-tokens/css/*.css` (
|
|
42
|
+
Theme CSS files are not bundled in this package — they're provided by `@marianmeres/design-tokens/css/*.css` (54 themes) and imported by `src/lib/index.css`.
|
|
43
43
|
|
|
44
44
|
---
|
|
45
45
|
|
|
@@ -126,15 +126,17 @@ Global tokens that control cross-component visual properties. Defined in `src/li
|
|
|
126
126
|
- [Architecture](./docs/architecture.md) — System design, data flow
|
|
127
127
|
- [Conventions](./docs/conventions.md) — Code standards, patterns
|
|
128
128
|
- [Tasks](./docs/tasks.md) — Common procedures
|
|
129
|
+
- [Testing](./docs/testing.md) — What we test, what we don't, the two-project split
|
|
130
|
+
- [Component test conventions](./docs/testing-components.md) — How to write a `*.svelte.test.ts`
|
|
129
131
|
|
|
130
132
|
### Domain Docs
|
|
131
133
|
|
|
132
|
-
- [Components](./docs/domains/components.md) —
|
|
134
|
+
- [Components](./docs/domains/components.md) — 78 component directories, Props pattern, snippets
|
|
133
135
|
- [Theming](./docs/domains/theming.md) — CSS tokens, dark mode, themes
|
|
134
136
|
- [CSS presets](./docs/domains/css-presets.md) — ratio-locked frame (letterbox), safe-area, scrollbar
|
|
135
|
-
- [Actions](./docs/domains/actions.md) —
|
|
137
|
+
- [Actions](./docs/domains/actions.md) — 16 Svelte directives
|
|
136
138
|
- [Attachments](./docs/domains/attachments.md) — `{@attach}` DOM helpers (preferred for new ones)
|
|
137
|
-
- [Utils](./docs/domains/utils.md) —
|
|
139
|
+
- [Utils](./docs/domains/utils.md) — 55 utility modules
|
|
138
140
|
|
|
139
141
|
### Reference
|
|
140
142
|
|
|
@@ -151,7 +153,7 @@ Global tokens that control cross-component visual properties. Defined in `src/li
|
|
|
151
153
|
| `src/lib/index.css` | CSS entry point |
|
|
152
154
|
| `src/lib/index.ts` | JS entry point |
|
|
153
155
|
| `src/lib/utils/design-tokens.ts` | Re-exports from `@marianmeres/design-tokens` |
|
|
154
|
-
| `@marianmeres/design-tokens/css/*.css` | Theme CSS files (
|
|
156
|
+
| `@marianmeres/design-tokens/css/*.css` | Theme CSS files (54 themes, `--stuic-` prefix) |
|
|
155
157
|
| `src/lib/components/Button/` | Reference component |
|
|
156
158
|
|
|
157
159
|
---
|
package/API.md
CHANGED
|
@@ -114,6 +114,25 @@ Expandable sections with exclusive or multi-open modes.
|
|
|
114
114
|
|
|
115
115
|
Responsive two-column layout with collapsible side panel, resizable width, and mobile-aware slide transitions.
|
|
116
116
|
|
|
117
|
+
#### `SplitPane`
|
|
118
|
+
|
|
119
|
+
Two panes with a draggable, keyboard-operable separator (ARIA window splitter) — `horizontal` (side by side, a width) or `vertical` (stacked, a height); nestable. Built on the `resizable` attachment.
|
|
120
|
+
|
|
121
|
+
| Prop | Type | Default | Description |
|
|
122
|
+
| --------------- | ---------------------------- | -------------- | ------------------------------------------------------------------------- |
|
|
123
|
+
| `start` / `end` | `Snippet` | — | The two panes' content |
|
|
124
|
+
| `orientation` | `"horizontal" \| "vertical"` | `"horizontal"` | Resize a width (side by side) or a height (stacked) |
|
|
125
|
+
| `primary` | `"start" \| "end"` | `"start"` | Which pane carries the size; the other one fills the rest |
|
|
126
|
+
| `size` | `number` | `50` | Bindable size of the primary pane in `units`; a stored size wins on mount |
|
|
127
|
+
| `units` | `"%" \| "px"` | `"%"` | Of the container, or px |
|
|
128
|
+
| `min` / `max` | `number` | `0` | Bounds in `units` (`0` = none) |
|
|
129
|
+
| `key` | `string \| number` | — | Persist the size (`storage`: `"session"` default, or `"local"`) |
|
|
130
|
+
| `disabled` | `boolean` | `false` | Inert separator, layout kept |
|
|
131
|
+
| `label` / `t` | `string` / `TranslateFn` | `"Resize"` | Accessible name of the separator (Slovak catalog bundled) |
|
|
132
|
+
| `onResize` | `(info) => void` | — | Every applied size |
|
|
133
|
+
|
|
134
|
+
Imperative `reset()` restores the mount size. Class slots `startClass` / `endClass` / `separatorClass`.
|
|
135
|
+
|
|
117
136
|
#### `Collapsible`
|
|
118
137
|
|
|
119
138
|
Expandable/collapsible content sections.
|
|
@@ -329,6 +348,96 @@ Money input whose canonical bindable `value` is an **integer in minor units** (e
|
|
|
329
348
|
|
|
330
349
|
The `formatMinorUnits`, `parseToMinorUnits`, and `money` helpers are exported for display elsewhere.
|
|
331
350
|
|
|
351
|
+
#### `Calendar`
|
|
352
|
+
|
|
353
|
+
Accessible month grid for picking a single date (`value`) or an inclusive range (`start` + `end`) — every value an ISO `YYYY-MM-DD` string. Roving-tabindex keyboard grid (arrows, Home/End, PageUp/PageDown ± Shift, Enter), `min` / `max` / `isDateDisabled`, several `months` side by side, `captionLayout="dropdown"` (native month + year selects), ISO week numbers, fixed 6-week height, a `renderDay` snippet, `locale` for the Intl day/month names and `t` for the UI texts (Slovak bundled). Date math from `@marianmeres/calendar-utils`. Used inside `FieldDate` / `FieldDateRange`; usable inline on its own.
|
|
354
|
+
|
|
355
|
+
| Prop | Type | Default | Description |
|
|
356
|
+
| ------------------------ | ------------------------------------ | ---------- | ---------------------------------------------------------------------- |
|
|
357
|
+
| `mode` | `"single" \| "range"` | `"single"` | Bind `value`, or `start` + `end` |
|
|
358
|
+
| `value` | `string \| null` | `null` | Bindable selected date (single) |
|
|
359
|
+
| `start`, `end` | `string \| null` | `null` | Bindable range ends (range) |
|
|
360
|
+
| `view` | `{ year, month }` | selection | Bindable first displayed month |
|
|
361
|
+
| `months` | `number` | `1` | Consecutive months side by side |
|
|
362
|
+
| `min`, `max` | `string \| null` | — | Selectable window (inclusive); days and navigation beyond are disabled |
|
|
363
|
+
| `isDateDisabled` | `(iso, cell) => boolean` | — | Blackout individual days |
|
|
364
|
+
| `weekStartsOn` | `1..7` | `1` | First day of the week (ISO, 1 = Monday) |
|
|
365
|
+
| `locale`, `zone` | `string` | browser | Intl names / which day is "today" |
|
|
366
|
+
| `captionLayout` | `"label" \| "dropdown"` | `"label"` | Caption text, or month + year selects |
|
|
367
|
+
| `showWeekNumbers` | `boolean` | `false` | ISO week column |
|
|
368
|
+
| `fixedWeeks` | `boolean` | `false` | Always 6 rows |
|
|
369
|
+
| `showToday`, `showClear` | `boolean` | `false` | Footer buttons |
|
|
370
|
+
| `onSelect` | `(value) => void` | — | Single mode pick / clear |
|
|
371
|
+
| `onRangeChange` | `({ start, end, complete }) => void` | — | Range mode pick / clear |
|
|
372
|
+
| `onViewChange` | `(view) => void` | — | Displayed month changed |
|
|
373
|
+
|
|
374
|
+
```svelte
|
|
375
|
+
<Calendar mode="range" bind:start bind:end months={2} min={todayIso()} />
|
|
376
|
+
```
|
|
377
|
+
|
|
378
|
+
Exports: `Calendar`, `CalendarProps`, `CalendarMode`, `CalendarCaptionLayout`, `CalendarDayState`, `CalendarRangeChange`, `CalendarDayCell`, `CalendarWeekday`, `createCalendarT`, `CALENDAR_MESSAGES_EN`, `CALENDAR_MESSAGES_SK`, and the ISO date helpers `isIsoDate`, `parseIsoDate`, `normalizeIsoDate`, `toIsoDate`, `todayIso`, `addDaysIso`, `addMonthsIso`, `compareIso`, `daysBetweenIso`, `rangeLengthIso`, `formatIsoDate`, `formatIsoDateRange` (types `IsoDate`, `IsoDateParts`, `YearMonth`).
|
|
379
|
+
|
|
380
|
+
#### `FieldDate`
|
|
381
|
+
|
|
382
|
+
Single-date field around `Calendar`: a trigger button showing the formatted date opens the calendar in a native dialog (default), or the calendar renders inline (`embedded`). The bound `value` is an ISO `YYYY-MM-DD` string; `name` goes on a hidden input carrying it, so the form submits the ISO date regardless of the display format. `required` / valid ISO / `min` / `max` / `isDateDisabled` are enforced by the field's validator (hidden inputs never validate natively).
|
|
383
|
+
|
|
384
|
+
| Prop | Type | Default | Description |
|
|
385
|
+
| --------------- | ---------------------------- | ------------------------- | ----------------------------------------------------------- |
|
|
386
|
+
| `value` | `string \| null` | `null` | Bindable ISO date |
|
|
387
|
+
| `name` | `string` | — | Hidden input name |
|
|
388
|
+
| `embedded` | `boolean` | `false` | Inline calendar instead of trigger + dialog |
|
|
389
|
+
| `closeOnSelect` | `boolean` | `true` | Close the dialog after a pick; `false` adds a "Done" button |
|
|
390
|
+
| `placeholder` | `string` | `t("placeholder_date")` | Trigger text while empty |
|
|
391
|
+
| `clearable` | `boolean` | `true` | Trailing × (dialog) / footer "Clear" (embedded) |
|
|
392
|
+
| `locale` | `string` | browser | Display text + calendar names |
|
|
393
|
+
| `formatOptions` | `Intl.DateTimeFormatOptions` | `{ dateStyle: "medium" }` | Display text format |
|
|
394
|
+
| `format` | `(iso) => string` | — | Custom display text |
|
|
395
|
+
| `months` | `number` | `1` | Months side by side |
|
|
396
|
+
| `calendarProps` | `Partial<CalendarProps>` | — | Anything else for the inner calendar |
|
|
397
|
+
| `validate` | `boolean \| ValidateOptions` | enabled | `false` disables the built-in guard; object merges |
|
|
398
|
+
| `t` | `TranslateFn` | English | i18n (see `createCalendarT`) |
|
|
399
|
+
| `onChange` | `(value) => void` | — | After a pick / clear through the UI |
|
|
400
|
+
|
|
401
|
+
Also forwards `min`, `max`, `isDateDisabled`, `weekStartsOn`, `weekendDays`, `zone`, `captionLayout`, `yearRange`, `showWeekNumbers`, `showOutsideDays`, `fixedWeeks`, `showToday`, `renderDay` to the calendar, and takes the usual field props (`label`, `description`, `renderSize`, `required`, `disabled`, `labelLeft`, class props, …). Methods: the imperative validate API plus `open()` / `close()`.
|
|
402
|
+
|
|
403
|
+
```svelte
|
|
404
|
+
<FieldDate
|
|
405
|
+
bind:value={delivery}
|
|
406
|
+
label="Delivery date"
|
|
407
|
+
name="delivery"
|
|
408
|
+
min={todayIso()}
|
|
409
|
+
required
|
|
410
|
+
/>
|
|
411
|
+
```
|
|
412
|
+
|
|
413
|
+
Exports: `FieldDate`, `FieldDateProps`, `FieldDateCalendarProps`.
|
|
414
|
+
|
|
415
|
+
#### `FieldDateRange`
|
|
416
|
+
|
|
417
|
+
Date-range field around a range-mode `Calendar`: same trigger + dialog / `embedded` presentations as `FieldDate`, binding `start` + `end` and submitting two hidden inputs (`nameStart`, `nameEnd`). First pick anchors, second completes (either order), a complete range closes the dialog (`closeOnSelect`). A half range fails validation whether or not the field is `required`. The default `months={2}` collapses to one below the `md` breakpoint.
|
|
418
|
+
|
|
419
|
+
| Prop | Type | Default | Description |
|
|
420
|
+
| ---------------------- | -------------------------- | ------- | ---------------------------------------- |
|
|
421
|
+
| `start`, `end` | `string \| null` | `null` | Bindable ISO range ends |
|
|
422
|
+
| `nameStart`, `nameEnd` | `string` | — | Hidden input names |
|
|
423
|
+
| `format` | `(start, end) => string` | — | Custom display text for a complete range |
|
|
424
|
+
| `months` | `number` | `2` | Months side by side (1 below `md`) |
|
|
425
|
+
| `onChange` | `({ start, end }) => void` | — | After a pick / clear through the UI |
|
|
426
|
+
|
|
427
|
+
Everything else as `FieldDate`.
|
|
428
|
+
|
|
429
|
+
```svelte
|
|
430
|
+
<FieldDateRange
|
|
431
|
+
bind:start={from}
|
|
432
|
+
bind:end={to}
|
|
433
|
+
label="Stay"
|
|
434
|
+
nameStart="from"
|
|
435
|
+
nameEnd="to"
|
|
436
|
+
/>
|
|
437
|
+
```
|
|
438
|
+
|
|
439
|
+
Exports: `FieldDateRange`, `FieldDateRangeProps`, `FieldDateRangeValue`.
|
|
440
|
+
|
|
332
441
|
#### `FieldTextarea`
|
|
333
442
|
|
|
334
443
|
Multi-line text input.
|
|
@@ -1516,10 +1625,12 @@ Visual feedback (class toggle) on drag-over.
|
|
|
1516
1625
|
|
|
1517
1626
|
### `resizableWidth`
|
|
1518
1627
|
|
|
1519
|
-
Make an element's width draggable.
|
|
1628
|
+
Make an element's width draggable. A thin wrapper over the `resizable` attachment (`axis: "x"`, see [Attachments](#attachments)) — the handle is a keyboard-operable `role="separator"`, `min` / `max` clamp, `key` persists.
|
|
1520
1629
|
|
|
1521
1630
|
```svelte
|
|
1522
|
-
<div use:resizableWidth={() => ({
|
|
1631
|
+
<div use:resizableWidth={() => ({ initial: 300, min: 200, max: 600, key: "sidebar" })}>
|
|
1632
|
+
Resizable
|
|
1633
|
+
</div>
|
|
1523
1634
|
```
|
|
1524
1635
|
|
|
1525
1636
|
### `trim`
|
|
@@ -1712,6 +1823,37 @@ When a step has `selector`, the tour uses `document.querySelector(selector)` to
|
|
|
1712
1823
|
|
|
1713
1824
|
---
|
|
1714
1825
|
|
|
1826
|
+
## Attachments
|
|
1827
|
+
|
|
1828
|
+
Svelte `{@attach}` helpers — the preferred form for new DOM behavior (reactive, composable, forwardable through components). Imported from the package root like everything else.
|
|
1829
|
+
|
|
1830
|
+
### `autoHeight`
|
|
1831
|
+
|
|
1832
|
+
Animates the host's height to its single child's natural height (pair it with a CSS `height` transition).
|
|
1833
|
+
|
|
1834
|
+
```svelte
|
|
1835
|
+
<div class="viewport" {@attach autoHeight}>
|
|
1836
|
+
<div>…variable-height content…</div>
|
|
1837
|
+
</div>
|
|
1838
|
+
```
|
|
1839
|
+
|
|
1840
|
+
### `longPress`
|
|
1841
|
+
|
|
1842
|
+
Factory: calls `onLongPress` when a touch / pen pointer stays down for `duration` ms (default 500) without moving beyond `moveTolerance` px.
|
|
1843
|
+
|
|
1844
|
+
```svelte
|
|
1845
|
+
<div {@attach longPress({ onLongPress: (e) => openAt(e.clientX, e.clientY) })}>…</div>
|
|
1846
|
+
```
|
|
1847
|
+
|
|
1848
|
+
### `resizable`
|
|
1849
|
+
|
|
1850
|
+
Factory: drag-resizable width (`axis: "x"`) or height (`axis: "y"`) with an ARIA window-splitter handle (arrows / Home / End / Enter), `min` / `max`, `px` or `%` of the parent, persisted under `key`; `handle` drives an element of your own, `onInit` hands out `set()` / `reset()`.
|
|
1851
|
+
|
|
1852
|
+
```svelte
|
|
1853
|
+
<aside {@attach resizable({ initial: 300, min: 200, max: 600, key: "sidebar" })}>…</aside>
|
|
1854
|
+
<div {@attach resizable({ axis: "y", units: "%", initial: 40 })}>…</div>
|
|
1855
|
+
```
|
|
1856
|
+
|
|
1715
1857
|
## Utilities
|
|
1716
1858
|
|
|
1717
1859
|
Import from `@marianmeres/stuic`.
|
|
@@ -2237,6 +2379,8 @@ Each component defines customization tokens. Override globally in `:root {}` or
|
|
|
2237
2379
|
| Button | `--stuic-button-*` | `bg`, `text`, `border`, `ring-color`, `radius`, `padding-x-{size}` |
|
|
2238
2380
|
| Switch | `--stuic-switch-*` | `accent` |
|
|
2239
2381
|
| Slider | `--stuic-slider-*` | `track`, `fill`, `thumb`, `tick`, `tick-on-fill`, `thickness`, `length`, `radius`, `fill-radius` |
|
|
2382
|
+
| RangeSlider | `--stuic-range-slider-*` | `track`, `fill`, `thumb`, `tick`, `tick-on-fill`, `thickness`, `length`, `radius`, `fill-radius`, `ring-width`, `ring-color` |
|
|
2383
|
+
| SplitPane | `--stuic-split-pane-*` | `separator-color`, `separator-color-hover`, `separator-thickness`, `separator-hit-area`, `grip-color`, `grip-color-hover`, `grip-border-color`, `grip-length`, `grip-thickness`, `grip-radius`, `ring-color`, `transition` |
|
|
2240
2384
|
| Input | `--stuic-input-*` | `accent`, `accent-error` |
|
|
2241
2385
|
| Progress | `--stuic-progress-*` | `bg`, `accent` |
|
|
2242
2386
|
| ListItemButton | `--stuic-list-item-button-*` | `bg`, `text`, `border`, `bg-hover`, `text-hover` |
|
package/README.md
CHANGED
|
@@ -167,15 +167,15 @@ See [API.md](API.md) for the full list of exported theme types (`ThemeSchema`, `
|
|
|
167
167
|
|
|
168
168
|
### Layout & Overlays
|
|
169
169
|
|
|
170
|
-
AppShell, Accordion, Backdrop, Modal, ModalDialog, Drawer, Collapsible, Header, SlidingPanels, Nav, WithSidePanel
|
|
170
|
+
AppShell, Accordion, Backdrop, Modal, ModalDialog, Drawer, Collapsible, Header, SlidingPanels, Nav, WithSidePanel, SplitPane
|
|
171
171
|
|
|
172
172
|
### Forms & Inputs
|
|
173
173
|
|
|
174
|
-
FieldInput, FieldMoney, FieldTextarea, FieldSelect, FieldCheckbox, FieldRadios, FieldFile, FieldAssets, FieldOptions, FieldKeyValues, FieldObject, FieldSwitch, FieldInputLocalized, FieldLikeButton, FieldPhoneNumber, FieldCountry, CronInput, Fieldset, LoginForm, LoginFormModal, RegisterForm, RegisterFormModal, LoginOrRegisterForm, LoginOrRegisterFormModal, EmailVerifyForm, OtpInput
|
|
174
|
+
FieldInput, FieldMoney, FieldDate, FieldDateRange, Calendar, FieldTextarea, FieldSelect, FieldCheckbox, FieldRadios, FieldFile, FieldAssets, FieldOptions, FieldKeyValues, FieldObject, FieldSwitch, FieldInputLocalized, FieldLikeButton, FieldPhoneNumber, FieldCountry, CronInput, Fieldset, LoginForm, LoginFormModal, RegisterForm, RegisterFormModal, LoginOrRegisterForm, LoginOrRegisterFormModal, EmailVerifyForm, OtpInput
|
|
175
175
|
|
|
176
176
|
### Buttons & Controls
|
|
177
177
|
|
|
178
|
-
Button, ButtonGroupRadio, Switch, Slider, TwCheck, ListItemButton, X
|
|
178
|
+
Button, ButtonGroupRadio, Switch, Slider, RangeSlider, TwCheck, ListItemButton, X
|
|
179
179
|
|
|
180
180
|
### Feedback & Notifications
|
|
181
181
|
|
|
@@ -206,6 +206,18 @@ Cart, Checkout (CheckoutProgress, CheckoutOrderSummary, CheckoutCartReview, Chec
|
|
|
206
206
|
|
|
207
207
|
`autogrow` · `validate` · `focusTrap` · `autoscroll` · `dimBehind` · `fileDropzone` · `highlightDragover` · `resizableWidth` · `spotlight` · `trim` · `typeahead` · `onSubmitValidityCheck` · `popover` · `tooltip` · `createTour` / `tourStep` (onboarding)
|
|
208
208
|
|
|
209
|
+
## Attachments
|
|
210
|
+
|
|
211
|
+
Svelte `{@attach}` helpers — preferred over new actions (reactive, composable, forwardable).
|
|
212
|
+
|
|
213
|
+
```svelte
|
|
214
|
+
<div {@attach autoHeight}>…</div>
|
|
215
|
+
<aside {@attach resizable({ initial: 300, min: 200, max: 600, key: "sidebar" })}>…</aside>
|
|
216
|
+
<div {@attach longPress({ onLongPress: (e) => openAt(e.clientX, e.clientY) })}>…</div>
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
`autoHeight` · `longPress` · `resizable`
|
|
220
|
+
|
|
209
221
|
## PWA safe-area insets
|
|
210
222
|
|
|
211
223
|
When a stuic app is installed and launched standalone (iOS Home Screen, Android/desktop PWA), the web view fills the entire screen, so edge-anchored chrome can render under the status bar / notch / home indicator. stuic ships an **opt-in** safe-area layer that is **inert in a normal browser tab** (`env()` → `0`) and only engages under `@media (display-mode: standalone), (display-mode: fullscreen)`.
|
|
@@ -1,42 +1,28 @@
|
|
|
1
|
+
import { type ResizableOptions } from "../attachments/resizable.js";
|
|
1
2
|
/**
|
|
2
|
-
* Options for the
|
|
3
|
+
* Options for the {@link resizableWidth} action — the `x`-axis
|
|
4
|
+
* {@link ResizableOptions} of the `resizable` attachment with the legacy
|
|
5
|
+
* `onResize` payload shape (`width` instead of `size`).
|
|
3
6
|
*/
|
|
4
|
-
export interface ResizableWidthOptions {
|
|
5
|
-
enabled?: boolean;
|
|
6
|
-
initial?: number;
|
|
7
|
-
min?: number;
|
|
8
|
-
max?: number;
|
|
9
|
-
units?: "px" | "%";
|
|
10
|
-
/** Reverses handle position (left instead of right) and drag direction */
|
|
11
|
-
reverse?: boolean;
|
|
12
|
-
key?: string | number | null | undefined;
|
|
13
|
-
storage?: "local" | "session";
|
|
14
|
-
handleClass?: string;
|
|
15
|
-
handleDragClass?: string;
|
|
7
|
+
export interface ResizableWidthOptions extends Omit<ResizableOptions, "axis" | "onResize"> {
|
|
16
8
|
onResize?: (info: {
|
|
17
9
|
width: number;
|
|
18
10
|
units: "px" | "%";
|
|
19
11
|
container: number;
|
|
20
12
|
}) => void;
|
|
21
|
-
debug?: (...args: unknown[]) => void;
|
|
22
13
|
}
|
|
23
14
|
/**
|
|
24
|
-
* A Svelte action that makes an element's width resizable via drag handle
|
|
15
|
+
* A Svelte action that makes an element's width resizable via a drag handle on its
|
|
16
|
+
* right (or, with `reverse`, left) edge.
|
|
25
17
|
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
* - Drag handle with visual feedback
|
|
32
|
-
* - Min/max width constraints
|
|
33
|
-
* - Support for px or % units
|
|
34
|
-
* - Optional storage persistence with custom key
|
|
35
|
-
* - Double-click handle to reset to initial width
|
|
36
|
-
* - Touch device support
|
|
18
|
+
* Kept for back-compat: it is a thin wrapper over the `resizable` attachment
|
|
19
|
+
* (`axis: "x"`), so it has everything the attachment has — the handle is a focusable
|
|
20
|
+
* `role="separator"` with arrow / Home / End / Enter keyboard resizing, Pointer Events
|
|
21
|
+
* cover mouse, touch and pen, `min` / `max` clamp, `key` persists, double-click resets.
|
|
22
|
+
* For new code (and for heights) use `{@attach resizable(...)}` directly.
|
|
37
23
|
*
|
|
38
24
|
* @param el - The element to make resizable
|
|
39
|
-
* @param fn - Function returning configuration options
|
|
25
|
+
* @param fn - Function returning configuration options (re-runs the action on reactive change)
|
|
40
26
|
*
|
|
41
27
|
* @remarks
|
|
42
28
|
* The `units` option should not be changed dynamically after initialization.
|
|
@@ -63,4 +49,4 @@ export interface ResizableWidthOptions {
|
|
|
63
49
|
* </div>
|
|
64
50
|
* ```
|
|
65
51
|
*/
|
|
66
|
-
export declare function resizableWidth(el:
|
|
52
|
+
export declare function resizableWidth(el: HTMLElement, fn?: () => ResizableWidthOptions): void;
|
|
@@ -1,22 +1,16 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { twMerge } from "../utils/tw-merge.js";
|
|
1
|
+
import { resizable } from "../attachments/resizable.js";
|
|
3
2
|
/**
|
|
4
|
-
* A Svelte action that makes an element's width resizable via drag handle
|
|
3
|
+
* A Svelte action that makes an element's width resizable via a drag handle on its
|
|
4
|
+
* right (or, with `reverse`, left) edge.
|
|
5
5
|
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
* - Drag handle with visual feedback
|
|
12
|
-
* - Min/max width constraints
|
|
13
|
-
* - Support for px or % units
|
|
14
|
-
* - Optional storage persistence with custom key
|
|
15
|
-
* - Double-click handle to reset to initial width
|
|
16
|
-
* - Touch device support
|
|
6
|
+
* Kept for back-compat: it is a thin wrapper over the `resizable` attachment
|
|
7
|
+
* (`axis: "x"`), so it has everything the attachment has — the handle is a focusable
|
|
8
|
+
* `role="separator"` with arrow / Home / End / Enter keyboard resizing, Pointer Events
|
|
9
|
+
* cover mouse, touch and pen, `min` / `max` clamp, `key` persists, double-click resets.
|
|
10
|
+
* For new code (and for heights) use `{@attach resizable(...)}` directly.
|
|
17
11
|
*
|
|
18
12
|
* @param el - The element to make resizable
|
|
19
|
-
* @param fn - Function returning configuration options
|
|
13
|
+
* @param fn - Function returning configuration options (re-runs the action on reactive change)
|
|
20
14
|
*
|
|
21
15
|
* @remarks
|
|
22
16
|
* The `units` option should not be changed dynamically after initialization.
|
|
@@ -44,162 +38,13 @@ import { twMerge } from "../utils/tw-merge.js";
|
|
|
44
38
|
* ```
|
|
45
39
|
*/
|
|
46
40
|
export function resizableWidth(el, fn) {
|
|
47
|
-
const HANDLE_CLS_BASE = [
|
|
48
|
-
"group",
|
|
49
|
-
"absolute top-0 bottom-0",
|
|
50
|
-
"w-[1px]",
|
|
51
|
-
"bg-black/20 hover:bg-black/30",
|
|
52
|
-
"dark:bg-white/10 dark:hover:bg-white/20",
|
|
53
|
-
"transition-colors duration-200",
|
|
54
|
-
"touch-none cursor-ew-resize",
|
|
55
|
-
].join(" ");
|
|
56
|
-
const DEFAULT_DRAG_HANDLE_CLS = [
|
|
57
|
-
"absolute h-[20px] w-[9px]",
|
|
58
|
-
"-translate-x-[4px] top-1/2 -translate-y-1/2",
|
|
59
|
-
"rounded border border-black/20 dark:border-white/20",
|
|
60
|
-
"bg-gray-300 group-hover:bg-gray-400",
|
|
61
|
-
"dark:bg-gray-600 dark:group-hover:bg-gray-500",
|
|
62
|
-
"transition-colors duration-200",
|
|
63
|
-
"touch-none cursor-ew-resize",
|
|
64
|
-
].join(" ");
|
|
65
|
-
function create_handle(el, handleClass, handleDragClass, reverse) {
|
|
66
|
-
const handle = document.createElement("div");
|
|
67
|
-
handle.setAttribute("data-handle", "true");
|
|
68
|
-
const dragHandle = document.createElement("div");
|
|
69
|
-
dragHandle.classList.add(...twMerge(DEFAULT_DRAG_HANDLE_CLS, handleDragClass).split(" "));
|
|
70
|
-
handle.appendChild(dragHandle);
|
|
71
|
-
el.appendChild(handle);
|
|
72
|
-
//
|
|
73
|
-
const positionCls = reverse ? "left-0" : "right-0";
|
|
74
|
-
handle.classList.add(...twMerge(HANDLE_CLS_BASE, positionCls, handleClass).split(" "));
|
|
75
|
-
return handle;
|
|
76
|
-
}
|
|
77
41
|
$effect(() => {
|
|
78
|
-
const {
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
//
|
|
86
|
-
let isResizing = false;
|
|
87
|
-
let startX = 0;
|
|
88
|
-
let startWidth = 0;
|
|
89
|
-
let containerW = undefined;
|
|
90
|
-
//
|
|
91
|
-
const handle = create_handle(el, handleClass, handleDragClass, reverse);
|
|
92
|
-
const container = el.parentElement;
|
|
93
|
-
// do we have a storage? if so, adjust the initial value...
|
|
94
|
-
const initialBackup = initial;
|
|
95
|
-
const _storage = get_storage(storage, key, initial);
|
|
96
|
-
if (_storage)
|
|
97
|
-
initial = _storage.current ?? initial;
|
|
98
|
-
// handlers/workers/helpers //////////////////////////////////////////////////////
|
|
99
|
-
function set_width(pxOrPercent) {
|
|
100
|
-
if (pxOrPercent) {
|
|
101
|
-
_debug(`set_width(${pxOrPercent})`);
|
|
102
|
-
set_width_px(units === "%" ? container.offsetWidth * (pxOrPercent / 100) : pxOrPercent);
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
function set_width_px(widthPx) {
|
|
106
|
-
_debug(`set_width_px(${widthPx})`);
|
|
107
|
-
containerW ??= container.offsetWidth;
|
|
108
|
-
const clamp = (value) => {
|
|
109
|
-
const _initial = value;
|
|
110
|
-
if (min)
|
|
111
|
-
value = Math.max(min, value);
|
|
112
|
-
if (max)
|
|
113
|
-
value = Math.min(max, value);
|
|
114
|
-
if (_initial !== value)
|
|
115
|
-
_debug("clamped", value, units);
|
|
116
|
-
return value;
|
|
117
|
-
};
|
|
118
|
-
let width;
|
|
119
|
-
if (units === "%") {
|
|
120
|
-
const widthPercent = Math.min(100, (widthPx / containerW) * 100); // convert to % (with 100 max)
|
|
121
|
-
width = clamp(widthPercent);
|
|
122
|
-
}
|
|
123
|
-
else {
|
|
124
|
-
width = clamp(widthPx);
|
|
125
|
-
}
|
|
126
|
-
el.style.width = `${width}${units}`;
|
|
127
|
-
_debug("new width", width, units);
|
|
128
|
-
const info = { width, units, container: containerW };
|
|
129
|
-
onResize?.(info);
|
|
130
|
-
// maybe save to storage
|
|
131
|
-
if (_storage)
|
|
132
|
-
_storage.current = width;
|
|
133
|
-
return info;
|
|
134
|
-
}
|
|
135
|
-
function resize_start(e) {
|
|
136
|
-
e.preventDefault(); // prevent scrolling on touch devices
|
|
137
|
-
isResizing = true;
|
|
138
|
-
//
|
|
139
|
-
const clientX = "touches" in e ? e.touches[0].clientX : e.clientX;
|
|
140
|
-
startX = clientX;
|
|
141
|
-
startWidth = parseInt(getComputedStyle(el).width, 10);
|
|
142
|
-
containerW = container.offsetWidth;
|
|
143
|
-
//
|
|
144
|
-
document.body.style.cursor = "ew-resize";
|
|
145
|
-
document.body.style.userSelect = "none";
|
|
146
|
-
}
|
|
147
|
-
function resize(e) {
|
|
148
|
-
if (!isResizing)
|
|
149
|
-
return;
|
|
150
|
-
e.preventDefault(); // prevent scrolling on touch devices
|
|
151
|
-
//
|
|
152
|
-
const clientX = "touches" in e ? e.touches[0].clientX : e.clientX;
|
|
153
|
-
const deltaX = clientX - startX;
|
|
154
|
-
const width = reverse ? startWidth - deltaX : startWidth + deltaX;
|
|
155
|
-
set_width_px(width);
|
|
156
|
-
}
|
|
157
|
-
function resize_stop() {
|
|
158
|
-
if (isResizing) {
|
|
159
|
-
isResizing = false;
|
|
160
|
-
containerW = undefined;
|
|
161
|
-
//
|
|
162
|
-
document.body.style.cursor = "";
|
|
163
|
-
document.body.style.userSelect = "";
|
|
164
|
-
}
|
|
165
|
-
}
|
|
166
|
-
function on_dblclick() {
|
|
167
|
-
set_width(initialBackup);
|
|
168
|
-
}
|
|
169
|
-
// initial styles ////////////////////////////////////////////////////////////////
|
|
170
|
-
el.style.position = "relative"; // so the handle will work
|
|
171
|
-
set_width(initial);
|
|
172
|
-
// listeners /////////////////////////////////////////////////////////////////////
|
|
173
|
-
// handle
|
|
174
|
-
handle.addEventListener("dblclick", on_dblclick);
|
|
175
|
-
handle.addEventListener("selectstart", (e) => e.preventDefault()); // prevent text selection during resize
|
|
176
|
-
// mouse
|
|
177
|
-
handle.addEventListener("mousedown", resize_start);
|
|
178
|
-
document.addEventListener("mousemove", resize);
|
|
179
|
-
document.addEventListener("mouseup", resize_stop);
|
|
180
|
-
// touch
|
|
181
|
-
handle.addEventListener("touchstart", resize_start, { passive: false });
|
|
182
|
-
document.addEventListener("touchmove", resize, { passive: false });
|
|
183
|
-
document.addEventListener("touchend", resize_stop);
|
|
184
|
-
document.addEventListener("touchcancel", resize_stop);
|
|
185
|
-
// cleanup ///////////////////////////////////////////////////////////////////////
|
|
186
|
-
return () => {
|
|
187
|
-
// mouse
|
|
188
|
-
document.removeEventListener("mousemove", resize);
|
|
189
|
-
document.removeEventListener("mouseup", resize_stop);
|
|
190
|
-
// touch
|
|
191
|
-
document.removeEventListener("touchmove", resize);
|
|
192
|
-
document.removeEventListener("touchend", resize_stop);
|
|
193
|
-
document.removeEventListener("touchcancel", resize_stop);
|
|
194
|
-
// will also remove it's own event listeners
|
|
195
|
-
handle.remove();
|
|
196
|
-
};
|
|
42
|
+
const { onResize, ...rest } = fn?.() || {};
|
|
43
|
+
return resizable({
|
|
44
|
+
...rest,
|
|
45
|
+
axis: "x",
|
|
46
|
+
onResize: onResize &&
|
|
47
|
+
(({ size, units, container }) => onResize({ width: size, units, container })),
|
|
48
|
+
})(el);
|
|
197
49
|
});
|
|
198
50
|
}
|
|
199
|
-
// helpers ///////////////////////////////////////////////////////////////////////////////
|
|
200
|
-
function get_storage(type, key, initialValue) {
|
|
201
|
-
if (key) {
|
|
202
|
-
return (type === "session" ? sessionStorageState : localStorageState)(`resizable-width-${key}`, initialValue);
|
|
203
|
-
}
|
|
204
|
-
return null;
|
|
205
|
-
}
|