Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
18 changes: 18 additions & 0 deletions .changeset/attachments-final-wave.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
---
'@layerstack/svelte-attachments': minor
'@layerstack/svelte-actions': patch
---

feat: Port the remaining actions — `styleProps`, `computedStyles`, `sticky`, `stickyContext`, `spotlight`, and `DomTracker` — completing `@layerstack/svelte-attachments`

`multi` is dropped rather than ported. Attachments compose natively, so several `{@attach}` on one element (or `createAttachmentKey` to pass them through a component's props) covers what it existed for.

`DomTracker` keeps its DOM-change tracking, which is what `sticky` and `spotlight` use to reverse exactly the changes they made. Its action map is replaced by `addAttachment()`, which folds another attachment's cleanup into the tracker, plus `addCleanup()` for anything else. The map's original purpose — keeping state across an action's `update()` calls — no longer applies, since an attachment re-runs from scratch rather than updating in place.

fix: `spotlight`, `stickyContext`, and `styleProps` now reverse what they applied. As actions they left classes and custom properties behind, which was invisible only because the element was usually destroyed anyway; an attachment re-runs on change, so the leftovers would accumulate.

fix: `styleProps` removes properties that are no longer present in the object. The action only did this from its second update onward, so properties dropped on the first change lingered.

fix: `stickyContext` no longer writes `NaNpx` when the computed `margin-top` is unresolved.

deprecate: `@layerstack/svelte-actions` is superseded by `@layerstack/svelte-attachments`. It continues to ship, and to receive fixes, while `@layerstack/svelte-table` is migrated off it.
23 changes: 23 additions & 0 deletions .changeset/dark-variant-system.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
---
'@layerstack/tailwind': patch
'@layerstack/ui': patch
'@layerstack/docs': patch
---

fix: Follow `prefers-color-scheme` in the `dark:` variant, not just `<html class="dark">`.

There were two disagreeing sources of truth for "is it dark". The theme palettes darken from
`@media (prefers-color-scheme: dark)` on `:root`, which needs no JavaScript, but the `dark:` variant
was keyed on the `dark` class, which is only ever set by JavaScript. Any moment the two disagreed —
before hydration, or permanently when no dark themes are registered — produced dark surfaces with
every `dark:` utility still in its light state. Most visibly, code blocks styled by
`dark:text-(--shiki-dark)` rendered near-black tokens on a dark background.

The variant now mirrors the palettes: an explicit `[data-theme]` selection wins, and everything else
follows the system setting. `@layerstack/docs`' `Code` component gets the same treatment for its
hand-written `html.dark .shiki` rules.

Also fixes `ThemeInit` on client-only apps. It injects its pre-paint script through `{@html}`, and
the browser never executes a `<script>` inserted that way — so with `ssr = false` the guard was
inert. `@layerstack/tailwind` now exports `applyInitialTheme`, which `ThemeInit` calls directly in
the browser.
18 changes: 18 additions & 0 deletions .changeset/docs-parity.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
---
'@layerstack/docs-site': patch
---

docs: Close the concept gaps against the Svelte UX docs.

Every old component page was compared against the new one and the missing concepts were added —
around 130 new examples across ~55 components, consolidated so one example usually covers what used
to be several sections. Notable additions: `Progress` color/track/color-by-value, `Month`,
`MonthList`, and `YearList` selection and disabled-date shapes, `Steps` points and colors,
`Timeline` layouts, `TextField` adornments/multiline/debounce, `SelectField` states and non-string
option values, `MultiSelect` option variants and slots, `Drawer` placement and persistence,
`Selection` select-all/single/max, and the `Gooey`/`Shine`/`Tilt` effect parameters.

Also fixes five pre-existing type errors in `Table`, `TreeList`, `Popover`, and `Badge` examples.

Examples now collapse their source by default — only the first example on a page keeps `showCode`,
since it doubles as the "how do I use this" sample. The rest are one click away behind **Show code**.
13 changes: 13 additions & 0 deletions .changeset/form-state.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
---
'@layerstack/svelte-state': minor
---

feat: Add `FormState` — the runes replacement for `@layerstack/svelte-stores`' `formStore`.

`draft` is a deeply reactive copy that inputs bind to directly, so the `refresh()`/`current` dance
the immer-draft-based store required is gone. `commit()` validates against an optional schema and
promotes the draft to `state`; `revert()`, `revertAll()`, and `undo()` walk it back. `isDirty` and
`canUndo` replace `dirtyStore`.

The schema is typed structurally (anything with a zod-compatible `safeParse`), so the package
depends on neither zod nor immer.
5 changes: 5 additions & 0 deletions .changeset/layerstack-ui.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@layerstack/ui': minor
---

feat: Add `@layerstack/ui` package — Svelte 5 successor to `svelte-ux`, starting with `Icon`, the component settings/theme system (converted to runes), and shared types
5 changes: 5 additions & 0 deletions .changeset/real-keys-battle.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@layerstack/svelte-attachments': minor
---

feat: Add `@layerstack/svelte-attachments` package
18 changes: 18 additions & 0 deletions .changeset/spotlight-shipped-css.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
---
'@layerstack/svelte-attachments': minor
'@layerstack/svelte-actions': patch
---

fix: `spotlight` and `scrollShadow` rendered nothing under Tailwind v4

Both drew their effect entirely from Tailwind utilities applied at runtime — `before:*` for the spotlight's gradients, `after:*` for the scroll shadows. Tailwind only emits utilities it finds while scanning source files, and a class added from a library's JavaScript is never scanned, so none of those rules existed and the effects were invisible. Tailwind v3 hid this, because apps listed the package under `content` and so scanned its source; v4's `@source` does not cover installed packages.

Each attachment now ships its own CSS. The one rule that cannot be expressed inline — the `::before`/`::after` that draws the effect — is injected into the document on first use as a constructable stylesheet, falling back to a `<style>` element, and only once per document or shadow root. Nothing depends on a stylesheet import or on a CSS framework, which also makes these usable outside Tailwind entirely.

The injected rules are wrapped in `@layer layerstack` and their selectors in `:where()`, so they carry effectively no specificity and any app rule wins. The layer alone is not enough — a layer declared from an injected sheet sorts after one the app declared earlier, such as Tailwind's `utilities`. Elements are marked with a data attribute (`data-spotlight`, `data-scroll-shadow`) rather than a class, and options are written as inline custom properties (`--default-spotlight-*`, `--hover-spotlight-*`). Setting `--spotlight-*` directly still works and still takes precedence.

`injectStyles` is exported for anyone writing an attachment with the same problem.

feat: Port `scrollShadow` and `scrollFade` to `@layerstack/svelte-attachments`, which had only `scrollIntoView`

`scrollFade` no longer relies on an `overflow-auto` class existing. Both now set `position`/`overflow` only when the element does not already have them, rather than forcing their own, and clear what they applied on cleanup — which the actions never did.
24 changes: 24 additions & 0 deletions .changeset/state-final-wave.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
---
'@layerstack/svelte-state': minor
'@layerstack/svelte-stores': patch
---

feat: Port the remaining stores — `DebouncedState`, `LocalState`, `PromiseState`, `FetchState`, `GraphState`, `QueryParamState`, and `QueryParamsState` — completing `@layerstack/svelte-state`

`changeStore`, `dirtyStore`, and `mapStore` are dropped rather than ported. Runes cover them directly: `$effect` with a captured previous value, a `$derived` comparison against the initial value, and `SvelteMap` from `svelte/reactivity`. `matchMedia` and its presets were already covered by `MediaQueryPresets` over Svelte's `MediaQuery`.

Where a store took another store, the class takes a getter so the dependency is tracked — `debounceStore(value)` becomes `new DebouncedState(() => value)`.

`QueryParamsState` reads `page` from `$app/state` rather than the `$app/stores` readable, and takes `gotoOptions` to forward navigation options. The store passed the whole page object in `goto`'s options position instead.

`FetchState.dispose()` replaces the cleanup the store ran when its last subscriber went away, since nothing subscribes any more. Wire it up with `$effect(() => state.dispose)` where a shared `FetchErrors` collection needs pruning.

fix: `fetchStore` and `graphStore` sent, and matched against, a `Content-Type` of `application/json.js` — a mangled `application/json`. No real response ever matched it, so JSON bodies fell through to `arrayBuffer()` rather than being parsed.

fix: `promiseStore` raced the promise against an immediate value to detect one that had already settled, which made an already-rejected promise reject the race itself. That escaped as an unhandled rejection and left `error` unset. Detection no longer runs through the race.

fix: `promiseStore` claimed the current promise only after that race, so two calls in the same tick could both proceed and the loser could overwrite the winner. `PromiseState` claims it synchronously.

fix: `fetchStore` re-added the previous error to the shared errors collection on every subsequent request, since the `loading: true` update carries the error forward. The collection could only grow.

deprecate: `@layerstack/svelte-stores` is superseded by `@layerstack/svelte-state`. It continues to ship, and to receive fixes.
8 changes: 8 additions & 0 deletions .changeset/svelte-attachments-overlay.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
'@layerstack/svelte-attachments': minor
'@layerstack/svelte-table': patch
---

feat: Add `portal`, `popover`, and `scrollIntoView` attachments. `popover` takes an `onClickOutside` callback in place of the `clickOutside` custom event.

`tableOrderStore().onHeaderClick` now accepts either `{ column }` or a `CustomEvent`-shaped `{ detail: { column } }`, so it works with both `@layerstack/ui`'s `Table` and Svelte UX's.
29 changes: 29 additions & 0 deletions .changeset/theme-desync.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
---
'@layerstack/svelte-state': patch
'@layerstack/svelte-stores': patch
'@layerstack/tailwind': patch
'@layerstack/docs': patch
---

fix: Stop the fallback settings from stripping the applied theme, and derive `dark:` from the themes.

`getSettings()` builds a fallback outside a `settings()` provider, and that fallback constructed a
theme store with **no themes configured**. Its constructor still ran, read `localStorage`, wrote
`<html data-theme="dark">`, and then — because `[].includes('dark')` is false — *removed* the `dark`
class the real instance had just applied. The result was `data-theme="dark"` with no `dark` class:
a dark palette with every `dark:` utility still light, most visibly light code tokens on a dark
background. Whether it happened at all depended on when the fallback was first touched, which is why
it was intermittent, and why toggling the theme fixed it.

A theme store with nothing configured is now inert — it never touches the document or
`localStorage`. A stored theme the app no longer defines is also ignored rather than applied, since
it cannot be classified as light or dark.

The `dark:` variant is now generated from the themes themselves. A theme declares its darkness with
`color-scheme: dark`, so that is the single source of truth: `@custom-variant dark` lists every dark
`[data-theme]` alongside `.dark` and the `prefers-color-scheme` fallback. The palette and the variant
can no longer disagree, whatever JavaScript does.

`@layerstack/docs` moves its Shiki color rules out of `Code.svelte`'s `<style>` block — Svelte
compiles those, so Tailwind never saw the `dark:` variant there — and into `styles.css`, which also
covers the runtime-highlighted blocks that the markdown-only rule missed.
5 changes: 5 additions & 0 deletions .changeset/theme-state.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@layerstack/svelte-state': patch
---

feat: Add `ThemeState` (replaces `createThemeStore` from `@layerstack/svelte-stores`) and export a `breakpoints` constant
7 changes: 7 additions & 0 deletions .changeset/ui-code-copybutton.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
'@layerstack/ui': minor
---

feat: Add `Code` and `CopyButton`. `shiki` and `@shikijs/transformers` are optional peer dependencies — `Code` renders plain text when they are not installed.

`ApiDocs`, `StoreSubscribe`, `State`, and `Preview` are not being migrated from Svelte UX; see `MIGRATION.md` for replacements.
12 changes: 12 additions & 0 deletions .changeset/ui-form.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
---
'@layerstack/ui': minor
---

feat: Migrate `Form`, the last remaining Svelte UX component.

- The nine slot props collapse to a single `{ form }` snippet argument — a `FormState` carrying
`draft`, `state`, `errors`, `isDirty`, `canUndo`, `commit`, `revert`, `revertAll`, and `undo`.
- `on:change` is the `onChange(value)` callback prop, and no longer fires on mount.
- `refresh()`/`current` are gone: `form.draft` is deeply reactive, so `bind:value={form.draft.name}`
is enough.
- A `form` prop accepts externally owned `FormState`, for reading or driving the form from outside.
15 changes: 15 additions & 0 deletions .changeset/ui-settings-rename.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
---
'@layerstack/ui': minor
---

fix: Rename the `Settings` class to `SettingsState` and move it to `settingsState.svelte.ts`.

Two collisions, both of which broke `import { Settings } from '@layerstack/ui'`:

- The class and the `Settings.svelte` component both exported as `Settings` from the package root,
and the class won — so importing `Settings` gave you the class, not the component.
- `Settings.svelte` and `settings.svelte.ts` both emit `settings.svelte.d.ts`, which are the same
file on case-insensitive filesystems. The component's declaration was being overwritten by the
module's, so its props did not type-check for consumers.

`settings()`, `getSettings()`, and `SettingsOptions` are unchanged.
8 changes: 8 additions & 0 deletions .changeset/ui-wave-1.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
'@layerstack/ui': minor
'@layerstack/svelte-state': patch
---

feat: Migrate the layout/primitive components — `Avatar`, `Breadcrumb`, `Card`, `DividerDot`, `EmptyMessage`, `Grid`, `Header`, `Kbd`, `Maybe`, `ScrollContainer`, `SectionDivider`, `Selection`, `Stack`, `Toggle`, and `ViewportCenter`

`SelectionState.all` is now reactive, so `isAllSelected()` and `isAnySelected()` re-evaluate when the set of values changes.
13 changes: 13 additions & 0 deletions .changeset/ui-wave-2.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
---
'@layerstack/ui': minor
'@layerstack/svelte-attachments': minor
'@layerstack/svelte-state': patch
---

feat: Migrate `Badge`, `Collapse`, `Duration`, `ExpansionPanel`, `InfiniteScroll`, `Lazy`, `Overflow`, `Paginate`, `Progress`, and `TreeList`

Adds the `intersection`, `resize`, `mutate`, `overflow`, `remainingViewportHeight`, and `remainingViewportWidth` attachments, each replacing a custom DOM event with a callback.

fix: `PaginationState` was not reactive — `page`, `perPage`, and `total` were plain fields, so nothing derived from them (`totalPages`, `from`, `to`, `hasNext`, ...) updated.

fix: `TimerState.start()` called the global `stop()` instead of `this.stop()`, which aborted in-flight page loads and left the previous interval running.
5 changes: 5 additions & 0 deletions .changeset/ui-wave-3.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@layerstack/ui': minor
---

feat: Migrate `Checkbox`, `NavItem`, `NumberStepper`, `Radio`, `RangeField`, and `Switch`
7 changes: 7 additions & 0 deletions .changeset/ui-wave-4.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
'@layerstack/ui': minor
---

feat: Migrate `BarStack`, `Gooey`, `ScrollingValue`, `Shine`, `SpringValue`, `Tilt`, and `TweenedValue`

`SpringValue`, `TweenedValue`, and `ScrollingValue` now use the `Spring`/`Tween` classes from `svelte/motion` in place of the deprecated `spring`/`tweened` stores.
8 changes: 8 additions & 0 deletions .changeset/ui-wave-5.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
'@layerstack/ui': minor
'@layerstack/svelte-attachments': minor
---

feat: Migrate `RangeSlider`, `ToggleGroup`, `ToggleOption`, `TogglePanel`, and `ToggleButton`

Adds the `movable`, `longpress`, and `mouseCoords` attachments, replacing the `movestart`/`move`/`moveend` and `longpress` custom events with callbacks.
7 changes: 7 additions & 0 deletions .changeset/ui-wave-6.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
'@layerstack/ui': minor
---

feat: Migrate the theme, tab, step, timeline, and app-shell components — `AppBar`, `AppLayout`, `Drawer`, `LanguageSelect`, `ListItem`, `ResponsiveMenu`, `Settings`, `Step`, `Steps`, `Tab`, `Tabs`, `ThemeInit`, `ThemeSelect`, `ThemeSwitch`, `Timeline`, `TimelineEvent`, and `Tooltip`

`themeSet` is now the `onThemeSet` callback prop on `ThemeSelect`/`ThemeSwitch`, and `Drawer`'s `open`/`close`/`closeAttempt` events are the `onOpen`/`onClose`/`onCloseAttempt` props.
7 changes: 7 additions & 0 deletions .changeset/ui-wave-7.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
'@layerstack/ui': minor
---

feat: Migrate `Pagination`, `TableOfContents`, `Notification`, and `ErrorNotification`

`Pagination` now takes a `PaginationState` from `@layerstack/svelte-state` in place of a `paginationStore`.
7 changes: 7 additions & 0 deletions .changeset/ui-wave-8.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
'@layerstack/ui': minor
---

feat: Migrate the date components — `DateButton`, `DateField`, `DatePickerField`, `DateRangeDisplay`, `DateSelect`, `Month`, `MonthList`, `MonthListByYear`, and `YearList`

`dateChange` is now the `onDateChange` callback prop throughout, and `DateField`/`DatePickerField` report through `onChange`.
12 changes: 12 additions & 0 deletions .changeset/ui-wave-9.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
---
'@layerstack/ui': minor
---

feat: Port the select cluster — `SelectField`, `MenuButton`, `MenuField`, `MultiSelect`, `MultiSelectField`, `MultiSelectMenu`, `MultiSelectOption` — plus `DateRange`, `DateRangeField`, and `QuickSearch`.

Notable changes from Svelte UX:

- Every non-native event is now a callback prop: `onChange`, `onInputChange`, `onApply`, `onCancel`, `onClose`.
- `MultiSelect` tracks the last applied selection internally rather than through `dirtyStore`/`changeStore`, so `Apply` enables only when the staged selection actually differs.
- `SelectField` compares options by `value` rather than by reference. Reference comparison broke once `selected` became a bindable prop, since what is read back is a `$state` proxy of what was written, never the same object as the entry in `options`.
- `emptyDateRange` is now exported from the package root.
1 change: 1 addition & 0 deletions docs/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
generated/
7 changes: 4 additions & 3 deletions docs/content-collections.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import { createContentConfig } from '@layerstack/docs/content-collections';

export default createContentConfig({
// LayerStack is a multi-package monorepo; docs are authored as `reference` content
// organized by package under `src/content/reference/<package>/<name>.md`.
packageName: 'layerstack',
// LayerStack is a multi-package monorepo; docs for most packages are authored as `reference`
// content organized by package under `src/content/reference/<package>/<name>.md`.
// `packageName` drives the `components`/`utils` collections, which document `@layerstack/ui`.
packageName: 'ui',
repo: 'techniq/layerstack',
// `docs` -> repo root `packages`
packagesRoot: '../packages',
Expand Down
Loading
Loading