@texuli/core 0.1.0-next.2 → 0.1.0-next.3

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 (2) hide show
  1. package/README.md +139 -129
  2. package/package.json +1 -1
package/README.md CHANGED
@@ -1,144 +1,154 @@
1
1
  # @texuli/core
2
2
 
3
- **Layer L0: texels.** Framework-free TypeScript behavior and policy that every
4
- Texuli component is built from. No Svelte, no runtime dependencies.
5
- Published to npm as `0.1.0-next.2` under the `next`
6
- dist-tag (by CI, with provenance); no stable release (`latest` is reserved
7
- for 1.0.0), so install it with `@next`. The workspace package stays
8
- `"private": true`.
9
-
10
- ## What exists today
11
-
12
- Every texel in the table below is implemented, tested and documented in this
13
- package. The public API is exactly what the root barrel `src/index.ts`
14
- exports; each texel is also a per-area subpath export in `package.json`
15
- (`@texuli/core/<area>`), so consumers can import narrowly and tree-shake the
16
- rest.
17
-
18
- | Texel | Headline exports | Doc |
19
- | --- | --- | --- |
20
- | Announcer | `createAnnouncer`, `createLiveRegion`, `getAnnouncer`, `appendRegion` | [core-announcer](docs/core-announcer.md) |
21
- | Controllable state | `createControllableState` | [core-controllable](docs/core-controllable.md) |
22
- | Direction and locale | `directionOf`, `inlineSideToPhysical`, `createCollator`, `compareLocale`, `resolveMessages` | [core-dir-locale](docs/core-dir-locale.md) |
23
- | Dismiss | `bindDismissLayer`, `isPointerOutside`, `isFocusOutside`, `createDismissOutsideEvent`, `createOpenGestureGuard` | [core-dismiss](docs/core-dismiss.md) |
24
- | Editor model | `loadEditorDocument`, `normalizeDocument`, `validateEditorDocument`, `caret`, `clampSelection`, `inlineToTree`; transforms (`insertText`, `splitBlock`, `deleteBackward`, `toggleMark`, `setLink`, `replaceSelection`, `applyInputRules`…), `editorHistoryReducer`, `createEditorKeymap`, `fromMarkdown`/`toMarkdown` (subpaths `@texuli/core/editor-model`, `editor-transforms`, `editor-history`, `editor-keymap`, `editor-markdown`; not in the root barrel) | [core-editor](docs/core-editor.md) |
25
- | Environment | `isBrowser`, `getOwnerDocument`, `getOwnerWindow`, `listen`, `matchesMedia`, `requestFrame` | [core-env](docs/core-env.md) |
26
- | Focus scope | `createFocusScope` | [core-focus-scope](docs/core-focus-scope.md) |
27
- | Form bridge | `createFormBridge` | [core-form-bridge](docs/core-form-bridge.md) |
28
- | Ids | `createIdScope`, `createIdFamily`, `joinIds`, `ariaLabelledBy`, `ariaDescribedBy`, `ariaControls`, `ensureElementId`, `sanitizeIdPart` | [core-ids](docs/core-ids.md) |
29
- | Inert | `createInertRegistry`, `getInertRegistry`, `computeInertTargets` | [core-inert](docs/core-inert.md) |
30
- | Keyboard | `parseShortcut`, `matchesShortcut`, `formatShortcut`, `createShortcutMap`, `getNavigationIntent`, `normalizeKey`, `detectPlatform` | [core-keyboard](docs/core-keyboard.md) |
31
- | Layer stack | `createLayerStack`, `getLayerStack`, `decideEscape`, `bindLayerStackDocument`, `layerReceivesEscape` | [core-layer-stack](docs/core-layer-stack.md) |
32
- | Lazy chunks | `lazyChunk`, `hasLazyChunk`, `clearLazyChunk(s)` (subpath `@texuli/core/lazy`; not in the root barrel) | — |
33
- | Overlay engine | `createOverlay` (`OVERLAY_MODE_ATTR`, `OVERLAY_Z_BASE`); the reduced surface is the `@texuli/core/overlay-core` subpath | [core-overlay-core](docs/core-overlay-core.md), [overlay-engine](docs/overlay-engine.md) |
34
- | Portal | `createPortal` | [core-portal](docs/core-portal.md) |
35
- | Positioning (DOM) | `positionFloating`, `applyPosition`, `autoUpdate`, `observeMove`, `cssAnchorDeclarations`, `supportsCssAnchorPositioning` | [core-position-dom](docs/core-position-dom.md) |
36
- | Positioning (math) | `computePosition`, `place`, `shiftIntoBoundary`, `sizeToAvailable`, `listPlacements`, `resolvePlacementForDirection`, `rect` | [core-position-math](docs/core-position-math.md) |
37
- | Presence | `createPresence`, `prefersReducedMotion`, `hasPendingMotion`, `parseCssTimeList` | [core-presence](docs/core-presence.md) |
38
- | Roving focus + typeahead | `createRovingFocus`, `createTypeahead`, `stepRovingIndex`, `setRovingTabIndex`, `indexOfSelected` | [core-roving](docs/core-roving.md) |
39
- | Scroll lock | `lockScroll` | [core-scroll-lock](docs/core-scroll-lock.md) |
40
- | Tabbable | `isFocusable`, `isTabbable`, `getFocusableElements`, `getTabbableElements` | [core-tabbable](docs/core-tabbable.md) |
41
- | URL policy (links) | `safeHref`, `type HrefTrust` | [core-url-policy](docs/core-url-policy.md) |
42
- | URL policy (media) | `safeImageSrc`, `safeImageSrcset`, `safeMediaSrc`, `MAX_DATA_IMAGE_BYTES` | [core-url-policy](docs/core-url-policy.md) |
43
-
44
- The editor texels are not in the root barrel: the barrel measures 22.64 of
45
- its 24 KiB budget. `pnpm size` (2026-10-26) measures `editor-history` at
46
- 773 B and `editor-keymap` at 2.49 KiB, both within the 4 KiB per-texel
47
- default. `editor-markdown` (7.05 of 7.50 KiB), `editor-model` (7.83 of
48
- 8.50 KiB) and `editor-transforms` (10.36 of 11 KiB) are over that default
49
- and carry their own budget rows; every entry is within budget.
50
-
51
- The tables above name the headline exports per texel, not the full surface:
52
- read `src/index.ts` (and the per-area `index.ts`) for every exported symbol
53
- and type. The threat model behind the URL policy lives in
54
- [docs/SECURITY.md](../../docs/SECURITY.md).
55
-
56
- ## Examples
3
+ The framework-free foundation of [Texuli](https://texuli.com), an MIT-licensed
4
+ Svelte 5 component library by Dailybot, Inc. (*Small pieces. Infinite
5
+ possibilities.*). `@texuli/core` holds the **texels**: small, tested
6
+ TypeScript units of behavior and policy (focus management, roving focus,
7
+ layering and Escape handling, positioning, ids, keyboard shortcuts, URL
8
+ safety) that every Texuli primitive and component is built from.
57
9
 
58
- ```ts
59
- import {
60
- safeHref,
61
- createIdScope,
62
- ariaLabelledBy,
63
- getLayerStack,
64
- decideEscape,
65
- getTabbableElements,
66
- isTabbable,
67
- } from '@texuli/core';
68
-
69
- safeHref('https://example.com/a'); // 'https://example.com/a'
70
- safeHref('http://example.com'); // undefined (untrusted is the default)
71
- safeHref('/docs/button', 'developer'); // '/docs/button'
72
- safeHref('javascript:alert(1)', 'developer'); // undefined in every tier
10
+ Use it directly when you build your own components in any framework, or
11
+ when you need Texuli's URL policy to sanitize links and media from agents
12
+ or users. It has no runtime dependencies and imports no UI framework.
73
13
 
74
- const ids = createIdScope({ prefix: 'dialog' });
75
- const titleId = ids.next('title'); // scoped, SSR-stable
76
- ariaLabelledBy(titleId); // the id list for aria-labelledby
14
+ > **Status: preview.** Texuli is in development. The packages are published
15
+ > to npm as prereleases under the `next` dist-tag, and there is no stable
16
+ > release yet, so install with `@next`. APIs may still change between
17
+ > previews.
77
18
 
78
- const stack = getLayerStack();
79
- const layer = stack.register({ element: panelEl, label: 'dialog' });
80
- decideEscape(stack, { key: 'Escape' }); // reaches only the topmost layer
81
- layer.unregister();
19
+ ## Install
82
20
 
83
- const stops = getTabbableElements(dialogElement);
84
- isTabbable(stops[0]!); // true
21
+ ```bash
22
+ npm install @texuli/core@next
85
23
  ```
86
24
 
87
- ### URL policy tiers
88
-
89
- - **`untrusted`** (default, for agent or user content): only `https:`,
90
- `mailto:` and `#fragment`.
91
- - **`developer`** (for app-authored props): anything except `javascript:`,
92
- `vbscript:`, `data:` and any value opening with two slashes or
93
- backslashes in any mix (`//host`, `/\host`, `\/host`, `\\host`) or with
94
- any leading backslash, which browsers resolve off-origin.
95
- - Whitespace and ASCII control characters are stripped before the scheme is
96
- read, so `java\tscript:` is caught.
97
-
98
- Images and media use the element-specific policies: `safeImageSrc` allows
99
- HTTPS and relative assets from either tier, plus (developer tier only) HTTP,
100
- valid blob URLs, and PNG, JPEG, GIF, WebP or AVIF base64 data images up to
101
- 1 MiB decoded; SVG and non-image data URLs are always refused.
102
- `safeImageSrcset` accepts a comma-separated `srcset` only when every candidate
103
- is safe. `safeMediaSrc` restricts untrusted audio/video to blob URLs or exact
104
- origins from an HTTPS `allowedOrigins` list. Full contract:
105
- [core-url-policy](docs/core-url-policy.md).
106
-
107
- ## Layout
108
-
109
- ```text
110
- packages/core/
111
- ├── package.json exports "." and one subpath per texel; script: typecheck
112
- ├── tsconfig.json extends ../../tsconfig.base.json
113
- ├── docs/ one contract doc per texel (core-*.md) + overlay-engine.md
114
- ├── src/index.ts the public barrel (only what is exported here is public)
115
- ├── src/<area>/ one directory per texel (announcer, roving, layer-stack, …)
116
- └── tests/ 26 test files, one or more per texel, including hostile
117
- inputs for every policy that guards a sink
25
+ ```bash
26
+ pnpm add @texuli/core@next
27
+ # or
28
+ yarn add @texuli/core@next
118
29
  ```
119
30
 
120
- ## Rules
31
+ No peer dependencies and no CSS. The package is ESM-only and ships
32
+ TypeScript declarations.
121
33
 
122
- - Framework-free: no Svelte or other UI framework imports; standard DOM types
123
- are fine.
124
- - No runtime dependencies; no imports from any other workspace package (L0 is
125
- the bottom layer).
126
- - Policy functions fail closed and document their contract in JSDoc.
127
- - Every export ships with tests in `tests/<name>.test.ts`, including hostile
128
- inputs for anything that guards a sink.
129
- - Ids never use `Math.random` at module scope; scopes are per-root.
34
+ ## Quick start
130
35
 
131
- ## Testing
36
+ ```ts
37
+ import { ariaLabelledBy, createIdScope, safeHref } from '@texuli/core';
38
+ import { detectPlatform, matchesShortcut } from '@texuli/core/keyboard';
132
39
 
133
- ```bash
134
- pnpm vitest run packages/core # every core texel test
135
- pnpm vitest run packages/core/tests/roving.test.ts # one texel
136
- pnpm --filter @texuli/core typecheck # tsc -p tsconfig.json
137
- npx biome check packages/core # lint + format
40
+ // URL policy: untrusted (agent or user) content is the default tier.
41
+ safeHref('https://example.com/report'); // 'https://example.com/report'
42
+ safeHref('http://example.com'); // undefined (https only)
43
+ safeHref('/docs/button', 'developer'); // '/docs/button'
44
+ safeHref('javascript:alert(1)', 'developer'); // undefined in every tier
45
+
46
+ // Ids that match on the server and the client.
47
+ const ids = createIdScope({ prefix: 'dialog' });
48
+ const titleId = ids.next('title'); // 'dialog-0-title'
49
+ ariaLabelledBy(titleId); // 'dialog-0-title'
50
+
51
+ // Keyboard shortcuts written as data; Mod is Cmd on Apple platforms, Ctrl elsewhere.
52
+ const platform = detectPlatform(navigator);
53
+ window.addEventListener('keydown', (event) => {
54
+ if (matchesShortcut(event, 'Mod+K', platform)) {
55
+ // open your command palette
56
+ }
57
+ });
138
58
  ```
139
59
 
140
- Core is shared by every layer above it, so a change here also runs
141
- `pnpm check`, the primitives behavior suite and `pnpm build`
142
- ([TESTING_GUIDE](../../docs/TESTING_GUIDE.md)).
143
- To add a texel, follow
144
- [`.agents/skills/add-texel/SKILL.md`](../../.agents/skills/add-texel/SKILL.md).
60
+ Everything in the table below is available from the root entry
61
+ (`@texuli/core`) and from its own subpath (`@texuli/core/<subpath>`), except
62
+ the editor and lazy-chunk texels, which are subpath-only. Import subpaths to
63
+ keep bundles narrow.
64
+
65
+ ## What's inside
66
+
67
+ | Texel | Subpath | Headline exports |
68
+ | --- | --- | --- |
69
+ | URL policy | `url-policy` | `safeHref`, `safeImageSrc`, `safeImageSrcset`, `safeMediaSrc` |
70
+ | Ids | `ids` | `createIdScope`, `createIdFamily`, `ariaLabelledBy`, `ariaDescribedBy`, `ensureElementId` |
71
+ | Keyboard | `keyboard` | `parseShortcut`, `matchesShortcut`, `formatShortcut`, `createShortcutMap`, `getNavigationIntent` |
72
+ | Roving focus and typeahead | `roving` | `createRovingFocus`, `createTypeahead`, `setRovingTabIndex` |
73
+ | Focus scope | `focus-scope` | `createFocusScope` (trap and restore focus) |
74
+ | Tabbable | `tabbable` | `isFocusable`, `isTabbable`, `getTabbableElements` |
75
+ | Layer stack | `layer-stack` | `getLayerStack`, `decideEscape` (Escape reaches only the topmost layer) |
76
+ | Dismiss | `dismiss` | `bindDismissLayer`, `isPointerOutside`, `isFocusOutside` |
77
+ | Inert | `inert` | `createInertRegistry`, `computeInertTargets` |
78
+ | Scroll lock | `scroll-lock` | `lockScroll` |
79
+ | Portal | `portal` | `createPortal` |
80
+ | Overlay engine | `overlay`, `overlay-core` | `createOverlay` (combines the texels above for dialogs, popovers and menus); `createOverlayCore` is the reduced surface |
81
+ | Positioning (math) | `position-math` | `computePosition`, `place`, `shiftIntoBoundary`, `resolvePlacementForDirection` |
82
+ | Positioning (DOM) | `position-dom` | `positionFloating`, `autoUpdate`, `cssAnchorDeclarations` |
83
+ | Presence | `presence` | `createPresence`, `prefersReducedMotion` (enter and exit animations) |
84
+ | Announcer | `announcer` | `createAnnouncer`, `createLiveRegion` (screen reader announcements) |
85
+ | Controllable state | `controllable` | `createControllableState` (controlled and uncontrolled values) |
86
+ | Form bridge | `form-bridge` | `createFormBridge` (custom controls in native forms) |
87
+ | Direction and locale | `dir-locale` | `directionOf`, `inlineSideToPhysical`, `createCollator`, `resolveMessages` |
88
+ | Environment | `env` | `isBrowser`, `getOwnerDocument`, `listen`, `matchesMedia`, `requestFrame` |
89
+ | Lazy chunks | `lazy` | `lazyChunk`, `hasLazyChunk` (subpath only) |
90
+ | Rich text editor model | `editor-model`, `editor-transforms`, `editor-history`, `editor-keymap`, `editor-markdown` | Document model, transforms, undo history, keymap, Markdown import and export (subpath only) |
91
+
92
+ The table names the headline exports; each subpath's TypeScript
93
+ declarations list the full surface.
94
+
95
+ ## URL policy
96
+
97
+ Every Texuli component that renders a link or a media source passes it
98
+ through this policy, and you can use it the same way. Rejected values return
99
+ `undefined` (the policy fails closed).
100
+
101
+ - **`untrusted`** (the default, for agent or user content): only `https:`,
102
+ `mailto:` and `#fragment` links.
103
+ - **`developer`** (for app-authored values): anything except `javascript:`,
104
+ `vbscript:`, `data:`, and values that start with two slashes or
105
+ backslashes in any mix, or with any backslash, which browsers resolve
106
+ off-origin.
107
+ - Whitespace and ASCII control characters are stripped before the scheme is
108
+ read, so `java\tscript:` is caught.
109
+ - `safeImageSrc` refuses SVG and non-image `data:` URLs in every tier;
110
+ `safeMediaSrc` limits untrusted audio and video to blob URLs or exact
111
+ HTTPS origins you allow.
112
+
113
+ ## Key guarantees
114
+
115
+ - **Framework-free.** No Svelte, React or other UI framework imports, and no
116
+ runtime dependencies. Standard DOM types only.
117
+ - **SSR-friendly ids.** Id scopes are per render root with no module-level
118
+ randomness, so the server and the client produce the same ids; `isBrowser`
119
+ and the owner-document helpers let code skip DOM work on the server.
120
+ - **Accessibility building blocks.** Roving focus, focus scopes, inert
121
+ backgrounds and layered Escape handling implement the behavior the
122
+ WAI-ARIA Authoring Practices patterns require.
123
+ - **Right-to-left aware.** Direction helpers map logical sides to physical
124
+ ones, and positioning resolves placements for the current direction.
125
+
126
+ ## Documentation
127
+
128
+ - Website: [texuli.com](https://texuli.com) and the
129
+ [accessibility guide](https://texuli.com/guides/accessibility/)
130
+ - URL policy contract: [core-url-policy.md](https://github.com/DailybotHQ/texuli/blob/main/packages/core/docs/core-url-policy.md)
131
+ - Overlay engine: [overlay-engine.md](https://github.com/DailybotHQ/texuli/blob/main/packages/core/docs/overlay-engine.md)
132
+ - Roving focus: [core-roving.md](https://github.com/DailybotHQ/texuli/blob/main/packages/core/docs/core-roving.md)
133
+ - Keyboard shortcuts: [core-keyboard.md](https://github.com/DailybotHQ/texuli/blob/main/packages/core/docs/core-keyboard.md)
134
+ - Positioning: [core-position-math.md](https://github.com/DailybotHQ/texuli/blob/main/packages/core/docs/core-position-math.md)
135
+ and [core-position-dom.md](https://github.com/DailybotHQ/texuli/blob/main/packages/core/docs/core-position-dom.md)
136
+ - Rich text editor model: [core-editor.md](https://github.com/DailybotHQ/texuli/blob/main/packages/core/docs/core-editor.md)
137
+ - Every texel's contract: [packages/core/docs](https://github.com/DailybotHQ/texuli/tree/main/packages/core/docs)
138
+ - Security model: [SECURITY.md](https://github.com/DailybotHQ/texuli/blob/main/docs/SECURITY.md)
139
+
140
+ ## Related packages
141
+
142
+ - [`@texuli/primitives`](https://www.npmjs.com/package/@texuli/primitives): headless Svelte 5 primitives built on these texels.
143
+ - [`texuli`](https://www.npmjs.com/package/texuli): styled, ready-to-use components.
144
+ - [`@texuli/tokens`](https://www.npmjs.com/package/@texuli/tokens): the theme engine and design tokens.
145
+
146
+ ## Contributing
147
+
148
+ Source, issues and contribution guidelines live at
149
+ [github.com/DailybotHQ/texuli](https://github.com/DailybotHQ/texuli). Report
150
+ bugs at [github.com/DailybotHQ/texuli/issues](https://github.com/DailybotHQ/texuli/issues).
151
+
152
+ ## License
153
+
154
+ MIT © Dailybot, Inc.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@texuli/core",
3
- "version": "0.1.0-next.2",
3
+ "version": "0.1.0-next.3",
4
4
  "description": "Texels: framework-free behavior and policy that every Texuli component is built from.",
5
5
  "keywords": [
6
6
  "svelte",