You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
114 lines
11 KiB
114 lines
11 KiB
# Continue — runed + tabbable → own libraries (0-dependency)
|
|
|
|
**Status 2026-07-03:** F1 + F2 + F3 **DONE and verified**. The port is complete —
|
|
runed + tabbable are fully removed and reimplemented in-house. (F1+F2 committed as
|
|
`d0f41fb2`; F3 commit follows.)
|
|
Goal: remove the two runtime deps that break the "framework depends on nobody"
|
|
doctrine — `runed@0.37.1` and `tabbable@^6.2.0` — by porting them into our own
|
|
code (same move as float → `$ethereal`). Decision: port the **whole** runed
|
|
library, not just what we use, but **fold into existing homes** (no parallel API)
|
|
and **rebuild DOM-reactive utilities on ActiveDom** (window-correct, never
|
|
`defaultWindow`).
|
|
|
|
## Locked architecture (where each piece lives)
|
|
|
|
| Family | Home | Notes |
|
|
| --------------------------------------- | -------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
| Pure reactive runes (no DOM) | `$libs/reactive` | grows the existing alias |
|
|
| DOM-reactive runes | `$adom` (`src/arts/adom`) | rebuilt on ActiveDom, pattern `{ dom }` like `FocusScope` |
|
|
| Utilities that already exist in `$adom` | reuse, don't duplicate | `listen`=useEventListener, `observeResize`=useResizeObserver, `observeMutation`=useMutationObserver, `observeIntersection`=useIntersectionObserver, `activeElement`, `requestFrame`=rAF |
|
|
| `tabbable` focus engine | `$libs/dom/tabbable-core.ts` | consumed by existing `tabbable.ts` wrapper, surfaced via `$adom` |
|
|
| `PersistedState` | `$storage` (`src/arts/storage`) | localStorage/sessionStorage |
|
|
| `useSearchParams` + `kit` subexport | **DROP** | app is `adapter-static`, SvelteKit-coupled |
|
|
| `boolAttr` | **already exists** as `boolToEmptyStrOrUndef` in `$libs/dom/attrs.ts` — do NOT re-port |
|
|
| `extract` / `MaybeGetter` / `get` | **reuse** framework `toValue` / `MaybeActiveOrGetter` / `Getter` |
|
|
|
|
## DONE — F1 (dropped the deps) + F2 (pure runes)
|
|
|
|
**F1** — created: `src/libs/dom/tabbable-core.ts`, `src/libs/reactive/watch.svelte.ts`
|
|
(watch/watch.pre/watchOnce), `src/libs/reactive/context.ts` (Context),
|
|
`src/arts/adom/element-size.svelte.ts` (ElementSize, rebuilt window-correct).
|
|
Rewired ~33 `watch` + 3 `Context` imports (runed → `$libs/reactive`), floating
|
|
(`ElementSize` → `$adom`), focus-scope (`tabbable` → `$adom`). Removed both from
|
|
`package.json`.
|
|
|
|
**F2** — created in `$libs/reactive`: `previous`, `is-mounted`, `on-cleanup`,
|
|
`interval` (was `useInterval`), `debounce`+`Debounced` (was `useDebounce`),
|
|
`throttle`+`Throttled` (was `useThrottle`), `state-history`,
|
|
`finite-state-machine`, `resource`. Renamed the `use*` prefix away (user OK'd).
|
|
Added `Setter<T>` to `types.ts`. Barrels updated: `$libs/dom/index`,
|
|
`$libs/reactive/index`, `$adom/index`.
|
|
|
|
## DONE — F3 (DOM-reactive runes → `$adom`)
|
|
|
|
**Refined the approach** from the original `{ dom }` idea to follow the committed
|
|
`ElementSize` precedent: each utility is window-correct by resolving the window
|
|
from its node via `getWindow(node)` (node-bound) or an optional `window`/`document`
|
|
option (global-scope ones) — no threaded `dom` param, no runed `defaultWindow`. Raw
|
|
observers/listeners live in the `arts/adom` implementation layer (allowed there,
|
|
like `active-dom.svelte.ts` itself). All 11 ported, type-clean (check=59), server
|
|
tests green (barrel loads). **`PersistedState` NOT ported** — the `$storage` art
|
|
already replaces it (its README names `runed/PersistedState` as the thing it
|
|
supersedes), same call as `boolAttr`→`boolToEmptyStrOrUndef`.
|
|
|
|
| runed utility | target file | maps onto | notes |
|
|
| -------------------- | ------------------------------------- | -------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------- |
|
|
| `ElementRect` | `$adom/element-rect.svelte.ts` | observeResize + observeMutation | like ElementSize; getBoundingClientRect via dom |
|
|
| `ActiveElement` | `$adom/active-element.svelte.ts` | listen(focusin/focusout) + `getActiveElement` (already in `$libs/dom/core`, shadow-piercing) | createSubscriber |
|
|
| `IsFocusWithin` | `$adom/is-focus-within.svelte.ts` | ActiveElement + node.contains | |
|
|
| `IsIdle` | `$adom/is-idle.svelte.ts` | listen + `debounce` (F2) | events on window/document |
|
|
| `IsDocumentVisible` | `$adom/is-document-visible.svelte.ts` | listen(visibilitychange) | |
|
|
| `IsInViewport` | `$adom/is-in-viewport.svelte.ts` | observeIntersection | |
|
|
| `onClickOutside` | `$adom/on-click-outside.svelte.ts` | listen(pointerdown/click/blur) + `debounce` + watch | biggest; iframe detection; `getActiveElement`/`isOrContainsTarget` already in `$libs/dom` |
|
|
| `PressedKeys` | `$adom/pressed-keys.svelte.ts` | listen(keydown/keyup/blur/visibilitychange) | createSubscriber |
|
|
| `ScrollState` | `$adom/scroll-state.svelte.ts` | listen(scroll/scrollend) + AnimationFrames + debounce | large; getComputedStyle via dom.getWindow |
|
|
| `TextareaAutosize` | `$adom/textarea-autosize.svelte.ts` | observeResize + watch + tick | creates hidden textarea; use dom for document |
|
|
| `AnimationFrames` | `$adom/animation-frames.svelte.ts` | dom.requestFrame/cancelFrame loop | fps-limit, pause/resume, reactive `fps` |
|
|
| ~~`PersistedState`~~ | **SKIPPED** | already in `$storage` | `createActiveStorage`+`localAdapter`/`sessionAdapter`/broadcast supersede it |
|
|
|
|
**Still open (optional, not blocking):** fold the useful runed extras into
|
|
`$adom`'s existing observer methods where they add value (not new API):
|
|
`once`/`pause`/`resume` + Firefox threshold fix in `observeIntersection`;
|
|
`takeRecords()` in `observeMutation`.
|
|
|
|
## How to re-obtain runed source (pruned from node_modules)
|
|
|
|
runed is gone from `node_modules`. To read source again:
|
|
`npm install runed@0.37.1 --no-save` (does NOT touch package.json), read
|
|
`node_modules/runed/dist/utilities/<name>/<name>.svelte.js` (+ `.svelte.d.ts` for
|
|
types) and `internal/utils/*`. **Gotcha:** the `--no-save` reinstall + the other
|
|
chat's running vite dev server caused an `EBUSY` lock and a transient
|
|
`svelte-check` crash; fix with `npm install` then
|
|
`rm -rf node_modules/runed node_modules/.runed-*`. GitHub mirror:
|
|
`github.com/svecosystem/runed/tree/main/packages/runed/src/lib`.
|
|
(A full F3 source dump may still exist at the previous session's
|
|
`scratchpad/runed-f3-source.txt`, but scratchpad is session-scoped — don't rely on it.)
|
|
|
|
## Verification baselines
|
|
|
|
- `npm run check` = **59 errors** — all pre-existing (connection, nav-menu,
|
|
time-picker, file-upload, editable/grid-list tests, web/routes demos, temas).
|
|
0 in ported files. Any of MY files appearing = regression.
|
|
- `npm run test` = **3920 pass / 24 fail** — all 24 pre-existing WIP: contracts
|
|
(13, menu-dial half-built), range-calendar (7, "incidencia 2026-05-20"), morfo
|
|
compile (1), chronos (1), calendar (1), soma-attr-audit (1, 5s timeout flaky).
|
|
|
|
## Gotchas (carry into F3)
|
|
|
|
- **`toValue` + union types:** `toValue`'s overloads don't unwrap a union-typed
|
|
`MaybeActiveOrGetter<T>` arg (it picks the `T→T` overload and returns the union).
|
|
Cast the result: `toValue(x) as number` / `as T`. Do NOT modify `toValue`
|
|
(684 consumers).
|
|
- **Prettier per subtree:** `$libs/dom` and `arts/adom`'s `active-dom.svelte.ts`
|
|
are written without semicolons and already fail `prettier --check`
|
|
pre-existingly; `$libs/reactive` + `arts/adom` newer files use semicolons.
|
|
Write new files with semicolons and run `prettier --write` on them (they'll be
|
|
compliant even if some neighbors aren't).
|
|
- **Layer rule:** soma/components import DOM only via `$adom`, never raw npm or
|
|
`$libs/dom`. Reactive utilities via `$libs/reactive`.
|
|
- **`.svelte.ts` extension** required for any file using runes ($state/$derived/$effect).
|
|
|
|
## Memory pointer
|
|
|
|
`memory/project_runed_tabbable_port_2026-07-03.md` has the full running log.
|