@danxbot/ui 1.0.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 (183) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +126 -0
  3. package/dist/fonts/bricolage-grotesque-0.woff2 +0 -0
  4. package/dist/fonts/bricolage-grotesque-1.woff2 +0 -0
  5. package/dist/fonts/bricolage-grotesque-2.woff2 +0 -0
  6. package/dist/fonts/figtree-0.woff2 +0 -0
  7. package/dist/fonts/figtree-1.woff2 +0 -0
  8. package/dist/fonts/figtree-2.woff2 +0 -0
  9. package/dist/fonts/figtree-3.woff2 +0 -0
  10. package/dist/fonts/jetbrains-mono-0.woff2 +0 -0
  11. package/dist/fonts/jetbrains-mono-1.woff2 +0 -0
  12. package/dist/fonts/jetbrains-mono-10.woff2 +0 -0
  13. package/dist/fonts/jetbrains-mono-11.woff2 +0 -0
  14. package/dist/fonts/jetbrains-mono-2.woff2 +0 -0
  15. package/dist/fonts/jetbrains-mono-3.woff2 +0 -0
  16. package/dist/fonts/jetbrains-mono-4.woff2 +0 -0
  17. package/dist/fonts/jetbrains-mono-5.woff2 +0 -0
  18. package/dist/fonts/jetbrains-mono-6.woff2 +0 -0
  19. package/dist/fonts/jetbrains-mono-7.woff2 +0 -0
  20. package/dist/fonts/jetbrains-mono-8.woff2 +0 -0
  21. package/dist/fonts/jetbrains-mono-9.woff2 +0 -0
  22. package/dist/fonts.css +187 -0
  23. package/dist/index.js +42425 -0
  24. package/dist/index.js.map +1 -0
  25. package/dist/styles.css +190 -0
  26. package/dist/types/components/Accordion.d.ts +15 -0
  27. package/dist/types/components/AppShell.d.ts +58 -0
  28. package/dist/types/components/Avatar.d.ts +154 -0
  29. package/dist/types/components/Badge.d.ts +73 -0
  30. package/dist/types/components/Button.d.ts +103 -0
  31. package/dist/types/components/Card.d.ts +193 -0
  32. package/dist/types/components/Choice.d.ts +112 -0
  33. package/dist/types/components/CodeEditor.d.ts +86 -0
  34. package/dist/types/components/Combobox.d.ts +37 -0
  35. package/dist/types/components/CommandPalette.d.ts +33 -0
  36. package/dist/types/components/DatePicker.d.ts +20 -0
  37. package/dist/types/components/Dialog.d.ts +117 -0
  38. package/dist/types/components/DragDrop.d.ts +60 -0
  39. package/dist/types/components/ErrorBoundary.d.ts +16 -0
  40. package/dist/types/components/Field.d.ts +37 -0
  41. package/dist/types/components/FileUpload.d.ts +22 -0
  42. package/dist/types/components/Icon.d.ts +40 -0
  43. package/dist/types/components/Input.d.ts +120 -0
  44. package/dist/types/components/Loading.d.ts +83 -0
  45. package/dist/types/components/MarkdownEditor.d.ts +24 -0
  46. package/dist/types/components/Menu.d.ts +57 -0
  47. package/dist/types/components/Navigation.d.ts +46 -0
  48. package/dist/types/components/NumberInput.d.ts +88 -0
  49. package/dist/types/components/OTPInput.d.ts +11 -0
  50. package/dist/types/components/Panel.d.ts +94 -0
  51. package/dist/types/components/Popover.d.ts +64 -0
  52. package/dist/types/components/Progress.d.ts +178 -0
  53. package/dist/types/components/Select.d.ts +53 -0
  54. package/dist/types/components/Slider.d.ts +203 -0
  55. package/dist/types/components/Spinner.d.ts +29 -0
  56. package/dist/types/components/Stat.d.ts +98 -0
  57. package/dist/types/components/StatusSelect.d.ts +23 -0
  58. package/dist/types/components/Surface.d.ts +110 -0
  59. package/dist/types/components/Table.d.ts +46 -0
  60. package/dist/types/components/Tabs.d.ts +84 -0
  61. package/dist/types/components/ThemeMenu.d.ts +4 -0
  62. package/dist/types/components/Toast.d.ts +46 -0
  63. package/dist/types/components/Toggle.d.ts +65 -0
  64. package/dist/types/components/Tree.d.ts +20 -0
  65. package/dist/types/components/calendar/CalendarMonth.d.ts +33 -0
  66. package/dist/types/components/calendar/CalendarTimeGrid.d.ts +75 -0
  67. package/dist/types/components/calendar/EventActions.d.ts +17 -0
  68. package/dist/types/components/calendar/format.d.ts +43 -0
  69. package/dist/types/components/calendar/index.d.ts +9 -0
  70. package/dist/types/components/calendar/palette.d.ts +17 -0
  71. package/dist/types/components/calendar/use-calendar-drag.d.ts +54 -0
  72. package/dist/types/components/calendar/use-event-keyboard.d.ts +24 -0
  73. package/dist/types/components/calendar/use-grid-navigation.d.ts +47 -0
  74. package/dist/types/components/calendar/use-now.d.ts +20 -0
  75. package/dist/types/components/chat/Attachment.d.ts +49 -0
  76. package/dist/types/components/chat/Chat.d.ts +60 -0
  77. package/dist/types/components/chat/Composer.d.ts +29 -0
  78. package/dist/types/components/chat/ContextMeter.d.ts +55 -0
  79. package/dist/types/components/chat/EffortControl.d.ts +23 -0
  80. package/dist/types/components/chat/Markdown.d.ts +22 -0
  81. package/dist/types/components/chat/Message.d.ts +59 -0
  82. package/dist/types/components/chat/MessageScroller.d.ts +13 -0
  83. package/dist/types/components/chat/ModelSelector.d.ts +44 -0
  84. package/dist/types/components/chat/types.d.ts +155 -0
  85. package/dist/types/components/code-editor/context.d.ts +32 -0
  86. package/dist/types/components/code-editor/editing.d.ts +39 -0
  87. package/dist/types/components/code-editor/highlight.d.ts +20 -0
  88. package/dist/types/components/code-editor/recipe.d.ts +109 -0
  89. package/dist/types/components/code-editor/state.d.ts +14 -0
  90. package/dist/types/components/drag-drop/context.d.ts +45 -0
  91. package/dist/types/components/drag-drop/recipe.d.ts +121 -0
  92. package/dist/types/components/drag-drop/shift.d.ts +66 -0
  93. package/dist/types/components/markdown/Affordances.d.ts +76 -0
  94. package/dist/types/components/rbac/AccountMenu.d.ts +8 -0
  95. package/dist/types/components/rbac/DevToolsPopover.d.ts +5 -0
  96. package/dist/types/components/rbac/ProfilePage.d.ts +1 -0
  97. package/dist/types/components/rbac/RBACProvider.d.ts +64 -0
  98. package/dist/types/components/rbac/RolePermissionMatrix.d.ts +6 -0
  99. package/dist/types/components/rbac/RolesPermissionsPage.d.ts +1 -0
  100. package/dist/types/components/rbac/index.d.ts +7 -0
  101. package/dist/types/components/rbac/types.d.ts +26 -0
  102. package/dist/types/components/roadmap/PhaseManager.d.ts +19 -0
  103. package/dist/types/components/roadmap/RoadmapItemCard.d.ts +45 -0
  104. package/dist/types/components/roadmap/RoadmapPage.d.ts +9 -0
  105. package/dist/types/components/roadmap/RoadmapSwimlaneBoard.d.ts +9 -0
  106. package/dist/types/components/roadmap/UseCaseCard.d.ts +14 -0
  107. package/dist/types/components/roadmap/UseCaseLedgerPage.d.ts +21 -0
  108. package/dist/types/components/roadmap/chip-tones.d.ts +2 -0
  109. package/dist/types/components/roadmap/derive.d.ts +12 -0
  110. package/dist/types/components/roadmap/index.d.ts +9 -0
  111. package/dist/types/components/roadmap/types.d.ts +54 -0
  112. package/dist/types/icons/data.d.ts +1318 -0
  113. package/dist/types/index.d.ts +97 -0
  114. package/dist/types/lib/calendar/band-packer.d.ts +53 -0
  115. package/dist/types/lib/calendar/drag.d.ts +125 -0
  116. package/dist/types/lib/calendar/index.d.ts +4 -0
  117. package/dist/types/lib/calendar/lane-packer.d.ts +88 -0
  118. package/dist/types/lib/calendar/segments.d.ts +91 -0
  119. package/dist/types/lib/calendar/types.d.ts +166 -0
  120. package/dist/types/lib/cn.d.ts +7 -0
  121. package/dist/types/lib/dnd/announce.d.ts +132 -0
  122. package/dist/types/lib/dnd/autoscroll.d.ts +88 -0
  123. package/dist/types/lib/dnd/geometry.d.ts +78 -0
  124. package/dist/types/lib/dnd/index.d.ts +16 -0
  125. package/dist/types/lib/dnd/sensors/gesture.d.ts +37 -0
  126. package/dist/types/lib/dnd/sensors/keyboard.d.ts +50 -0
  127. package/dist/types/lib/dnd/sensors/pointer.d.ts +59 -0
  128. package/dist/types/lib/dnd/session.d.ts +99 -0
  129. package/dist/types/lib/dnd/types.d.ts +162 -0
  130. package/dist/types/lib/dnd/use-drag.d.ts +56 -0
  131. package/dist/types/lib/icons.d.ts +40 -0
  132. package/dist/types/lib/markdown/caret-map.d.ts +48 -0
  133. package/dist/types/lib/markdown/caret.d.ts +6 -0
  134. package/dist/types/lib/markdown/commands.d.ts +170 -0
  135. package/dist/types/lib/markdown/convert.d.ts +82 -0
  136. package/dist/types/lib/markdown/dom.d.ts +67 -0
  137. package/dist/types/lib/markdown/edit.d.ts +16 -0
  138. package/dist/types/lib/markdown/history.d.ts +47 -0
  139. package/dist/types/lib/markdown/hotkeys.d.ts +39 -0
  140. package/dist/types/lib/markdown/input.d.ts +15 -0
  141. package/dist/types/lib/markdown/keys.d.ts +34 -0
  142. package/dist/types/lib/markdown/paste.d.ts +9 -0
  143. package/dist/types/lib/markdown/position.d.ts +43 -0
  144. package/dist/types/lib/markdown/project.d.ts +88 -0
  145. package/dist/types/lib/markdown/reconcile.d.ts +63 -0
  146. package/dist/types/lib/markdown/surface.d.ts +177 -0
  147. package/dist/types/lib/persist.d.ts +40 -0
  148. package/dist/types/lib/preferences.d.ts +80 -0
  149. package/dist/types/lib/sim/createSimStore.d.ts +18 -0
  150. package/dist/types/lib/sim/index.d.ts +5 -0
  151. package/dist/types/lib/sim/latency.d.ts +10 -0
  152. package/dist/types/lib/sim/registry.d.ts +14 -0
  153. package/dist/types/lib/sim/settings.d.ts +11 -0
  154. package/dist/types/lib/sim/types.d.ts +3 -0
  155. package/dist/types/lib/syntax/index.d.ts +16 -0
  156. package/dist/types/lib/syntax/languages/bash.d.ts +1 -0
  157. package/dist/types/lib/syntax/languages/css.d.ts +1 -0
  158. package/dist/types/lib/syntax/languages/html.d.ts +17 -0
  159. package/dist/types/lib/syntax/languages/index.d.ts +33 -0
  160. package/dist/types/lib/syntax/languages/javascript.d.ts +22 -0
  161. package/dist/types/lib/syntax/languages/json.d.ts +1 -0
  162. package/dist/types/lib/syntax/languages/mongodb.d.ts +1 -0
  163. package/dist/types/lib/syntax/languages/plain.d.ts +9 -0
  164. package/dist/types/lib/syntax/languages/sql.d.ts +4 -0
  165. package/dist/types/lib/syntax/languages/vue.d.ts +1 -0
  166. package/dist/types/lib/syntax/languages/yaml.d.ts +1 -0
  167. package/dist/types/lib/syntax/markdown/frames.d.ts +145 -0
  168. package/dist/types/lib/syntax/markdown/grammar.d.ts +1 -0
  169. package/dist/types/lib/syntax/markdown/index.d.ts +4 -0
  170. package/dist/types/lib/syntax/markdown/inline.d.ts +21 -0
  171. package/dist/types/lib/syntax/markdown/parse.d.ts +11 -0
  172. package/dist/types/lib/syntax/markdown/render.d.ts +19 -0
  173. package/dist/types/lib/syntax/markdown/spans.d.ts +84 -0
  174. package/dist/types/lib/syntax/markdown/types.d.ts +108 -0
  175. package/dist/types/lib/syntax/registry.d.ts +37 -0
  176. package/dist/types/lib/syntax/tokenize.d.ts +17 -0
  177. package/dist/types/lib/syntax/types.d.ts +119 -0
  178. package/dist/types/lib/time/index.d.ts +3 -0
  179. package/dist/types/lib/time/plain.d.ts +61 -0
  180. package/dist/types/lib/time/types.d.ts +87 -0
  181. package/dist/types/lib/time/zone.d.ts +97 -0
  182. package/dist/types/lib/use-media-query.d.ts +29 -0
  183. package/package.json +130 -0
@@ -0,0 +1,47 @@
1
+ import type { SourceSelection } from "./position";
2
+ export interface HistoryEntry {
3
+ source: string;
4
+ selection: SourceSelection | null;
5
+ }
6
+ /** What produced an edit, for deciding whether two of them belong together. */
7
+ export type EditKind = "type" | "delete" | "compose" | "other";
8
+ export interface HistoryOptions {
9
+ /** Quiet time after which the next edit starts a new group. */
10
+ groupMs?: number;
11
+ /** Entries kept. Beyond this the oldest are dropped. */
12
+ limit?: number;
13
+ /** Injected so tests are deterministic; never read a clock directly. */
14
+ now?: () => number;
15
+ }
16
+ export declare class History {
17
+ private readonly entries;
18
+ private index;
19
+ private readonly groupMs;
20
+ private readonly limit;
21
+ private readonly now;
22
+ /** State of the group currently open for merging. */
23
+ private lastAt;
24
+ private lastKind;
25
+ private lastEnd;
26
+ constructor(initial: HistoryEntry, options?: HistoryOptions);
27
+ get canUndo(): boolean;
28
+ get canRedo(): boolean;
29
+ /** Entry count, for tests that assert coalescing rather than behaviour. */
30
+ get size(): number;
31
+ /**
32
+ * Records a state.
33
+ *
34
+ * `at` is where the edit happened; adjacency to the previous edit is what
35
+ * separates "still typing" from "went somewhere else and typed". Passing the
36
+ * position rather than deriving it from the diff is deliberate — a diff of
37
+ * two strings finds the shortest edit, which is not always the edit that
38
+ * happened, and coalescing decisions made from the wrong position are worse
39
+ * than none.
40
+ */
41
+ record(entry: HistoryEntry, kind: EditKind, at: number): void;
42
+ private shouldMerge;
43
+ /** Forces the next edit to open a new group — a selection change, a blur. */
44
+ break(): void;
45
+ undo(): HistoryEntry | null;
46
+ redo(): HistoryEntry | null;
47
+ }
@@ -0,0 +1,39 @@
1
+ import { type Command } from "./commands";
2
+ export type HotkeyGroup = "formatting" | "headings" | "lists" | "blocks" | "tables";
3
+ export interface Hotkey {
4
+ /**
5
+ * The chord this matches: lower-case, modifiers first, in the order ctrl,
6
+ * alt, shift.
7
+ *
8
+ * PUNCTUATION IS SPELLED BY PHYSICAL KEY (`bracketleft`, `comma`), not by the
9
+ * character it produces. The browser probe found why: with Shift held, the
10
+ * `[` key reports `event.key === "{"`, so a table keyed on `[` never matched
11
+ * and Ctrl+Shift+[ was silently inert. `event.code` is the same regardless of
12
+ * which character the modifier produced.
13
+ */
14
+ key: string;
15
+ /** What the help surface shows, which is the chord as a person types it. */
16
+ label: string;
17
+ description: string;
18
+ group: HotkeyGroup;
19
+ command: Command;
20
+ }
21
+ export declare const HOTKEYS: readonly Hotkey[];
22
+ /**
23
+ * The chords a key event could be, most specific first.
24
+ *
25
+ * TWO SPELLINGS, because neither alone is enough. The CHARACTER spelling is
26
+ * what a letter or digit chord needs — Ctrl+B is the letter B whatever key
27
+ * produced it. The PHYSICAL spelling is what punctuation needs: with Shift
28
+ * held, the `[` key reports `event.key === "{"`, so a table keyed on the
29
+ * character never matches and the chord is silently inert. The browser probe
30
+ * found exactly that: Ctrl+Shift+[ did nothing at all, and nothing in the unit
31
+ * suite could have seen it, because the commands themselves were correct.
32
+ *
33
+ * Modifiers in a FIXED order, because `ctrl+shift+s` and `shift+ctrl+s` are the
34
+ * same chord and a table keyed on free-form strings silently misses one of
35
+ * them. `metaKey` folds into `ctrl` so one table serves macOS, where the
36
+ * platform convention is Command for every one of these.
37
+ */
38
+ export declare function chordsOf(event: KeyboardEvent): string[];
39
+ export declare function hotkeyFor(event: KeyboardEvent): Hotkey | null;
@@ -0,0 +1,15 @@
1
+ import type { MarkdownSurface } from "./surface";
2
+ export interface InputOptions {
3
+ /**
4
+ * Called for an intent this layer cancelled but did not act on.
5
+ *
6
+ * Not a warning to be silenced — it is the list of what phase 4 owes. An
7
+ * editor that quietly ignores `formatBold` looks like one where Ctrl+B is
8
+ * broken, and this is how that gets noticed before a user reports it.
9
+ */
10
+ onUnhandled?: (inputType: string) => void;
11
+ }
12
+ /**
13
+ * Binds the input layer to a surface. Returns the unbind function.
14
+ */
15
+ export declare function attachInput(surface: MarkdownSurface, options?: InputOptions): () => void;
@@ -0,0 +1,34 @@
1
+ import type { CaretMap } from "./caret-map";
2
+ import { type Edit } from "./edit";
3
+ import type { SourceSelection } from "./position";
4
+ import type { Projection } from "./project";
5
+ export type KeyIntent = {
6
+ kind: "insert";
7
+ text: string;
8
+ } | {
9
+ kind: "backspace";
10
+ } | {
11
+ kind: "delete";
12
+ } | {
13
+ kind: "enter";
14
+ } | {
15
+ kind: "lineBreak";
16
+ } | {
17
+ kind: "tab";
18
+ } | {
19
+ kind: "shiftTab";
20
+ };
21
+ /** Everything a decision needs, and nothing it does not. */
22
+ export interface KeyContext {
23
+ source: string;
24
+ projection: Projection;
25
+ map: CaretMap;
26
+ }
27
+ /**
28
+ * The edit a keystroke produces, or null to let the caller decline the key.
29
+ *
30
+ * Null is a real answer and not a failure: Tab outside a list declines so focus
31
+ * can leave the editor, which is what makes the WCAG 2.1.2 keyboard trap not
32
+ * happen in the first place.
33
+ */
34
+ export declare function resolveKey(ctx: KeyContext, selection: SourceSelection, intent: KeyIntent): Edit | null;
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Converts one pasted HTML fragment to markdown.
3
+ *
4
+ * `parser` is injected so this stays testable and so it never reaches for a
5
+ * global — the editor passes its own document, and a test passes whatever it
6
+ * has. A module that grabs `window` is a module that cannot be tested without
7
+ * one.
8
+ */
9
+ export declare function htmlFragmentToMarkdown(html: string, parse: (html: string) => Document): string;
@@ -0,0 +1,43 @@
1
+ export interface DomPoint {
2
+ /**
3
+ * Usually a text node. It is an ELEMENT when the position has no text of its
4
+ * own to sit in — beside a picture, say, where every character of the
5
+ * construct is markup and every markup node is zero-width. An element point
6
+ * names a place BETWEEN children, which is a position the browser renders a
7
+ * caret for and `sourceOffsetAt` already knows how to read back.
8
+ */
9
+ node: Node;
10
+ offset: number;
11
+ }
12
+ /** Which side of a boundary an offset belongs to when two runs meet there. */
13
+ export type Association = "before" | "after";
14
+ export declare function domPointAt(root: Node, offset: number, association?: Association): DomPoint | null;
15
+ /**
16
+ * The source offset for a DOM point.
17
+ *
18
+ * Accepts an ELEMENT container as well as a text node, because that is what a
19
+ * browser hands back: a selection at the start of an empty paragraph, or one
20
+ * that landed between two children, is reported as `(element, childIndex)`.
21
+ * Resolving it here rather than at each call site is the difference between one
22
+ * correct conversion and six approximate ones.
23
+ *
24
+ * Returns null for a point in a subtree this module did not build, which is a
25
+ * REAL event — the browser creates nodes during composition and on paste — and
26
+ * the caller must handle it as one rather than substitute a plausible number.
27
+ */
28
+ export declare function sourceOffsetAt(root: Node, node: Node, offset: number): number | null;
29
+ export interface SourceSelection {
30
+ anchor: number;
31
+ head: number;
32
+ }
33
+ /** The current selection as source offsets, or null if it is not in `root`. */
34
+ export declare function readSelection(root: HTMLElement): SourceSelection | null;
35
+ /**
36
+ * Puts the selection at the given source offsets.
37
+ *
38
+ * DOES NOTHING IF IT ALREADY MATCHES. That is the first of the three rules both
39
+ * ProseMirror and CodeMirror carry verbatim: writing a selection that is already
40
+ * correct is itself a source of caret jumps, because some engines scroll,
41
+ * collapse or re-anchor on assignment even when nothing moved.
42
+ */
43
+ export declare function writeSelection(root: HTMLElement, selection: SourceSelection, association?: Association): boolean;
@@ -0,0 +1,88 @@
1
+ import type { Frame, FrameRecord } from "../syntax/markdown/frames";
2
+ /**
3
+ * A run of literal source characters.
4
+ *
5
+ * `syntax` is what the view collapses. It is not a rendering hint — it is the
6
+ * parser's own answer to "did any frame claim this as its text", so a run that
7
+ * says `syntax: false` is genuinely editable prose and one that says true is
8
+ * genuinely markup.
9
+ */
10
+ export interface PText {
11
+ type: "text";
12
+ start: number;
13
+ end: number;
14
+ text: string;
15
+ syntax: boolean;
16
+ /**
17
+ * The highlight class this run carries, inside a fenced block.
18
+ *
19
+ * Only ever set for code, and only when a tokenizer was supplied. Prose stays
20
+ * untyped so it stays a BARE text node — see ../markdown/dom.ts, constraint 3:
21
+ * every wrapper around prose is another reason to replace a node, and a
22
+ * replaced node is a cancelled composition.
23
+ */
24
+ token?: string;
25
+ }
26
+ /** How a fenced block's body is highlighted, if the caller wants it. */
27
+ export interface ProjectOptions {
28
+ /**
29
+ * Returns tokens with ABSOLUTE offsets into `source`, exactly as
30
+ * `lib/syntax`'s `tokenize` does.
31
+ *
32
+ * Injected rather than imported, and the reason is the same one the module
33
+ * header gives for not tokenizing at all before: reaching for the grammar
34
+ * REGISTRY from here would make a pure function's output depend on which
35
+ * languages happened to be registered when it ran. Passing it in keeps the
36
+ * projection reproducible and keeps this module's tests grammar-free.
37
+ */
38
+ highlight?: (code: string, language: string | null, at: number) => Array<{
39
+ start: number;
40
+ end: number;
41
+ type: string;
42
+ }>;
43
+ }
44
+ /** A frame with its projected children, in source order. */
45
+ export interface PElement {
46
+ type: "element";
47
+ frame: Frame;
48
+ start: number;
49
+ end: number;
50
+ children: PNode[];
51
+ }
52
+ export type PNode = PText | PElement;
53
+ /** The document root. `source` is kept so consumers never re-derive it. */
54
+ export interface Projection {
55
+ source: string;
56
+ children: PNode[];
57
+ /** Every frame, in the order the parser opened them. */
58
+ frames: readonly FrameRecord[];
59
+ }
60
+ /**
61
+ * Thrown when the parser's frames do not nest, which would make the projection
62
+ * ambiguous.
63
+ *
64
+ * This is a FAIL-LOUD, not a repair. A projector that silently reordered
65
+ * overlapping frames would produce a tree that reads correctly and maps the
66
+ * caret to the wrong character — the exact defect this whole design exists to
67
+ * make impossible, reintroduced by the code meant to prevent it.
68
+ */
69
+ export declare class ProjectionError extends Error {
70
+ constructor(message: string);
71
+ }
72
+ /**
73
+ * Projects a markdown string.
74
+ *
75
+ * The walk is linear over the SOURCE, descending into a frame when it starts
76
+ * and leaving when it ends. Every character is emitted exactly once, by
77
+ * exactly one of the two paths — inside a frame or at the document level —
78
+ * which is the invariant stated as an algorithm rather than as a test.
79
+ */
80
+ export declare function project(source: string, options?: ProjectOptions): Projection;
81
+ /**
82
+ * Reads a projection's text back, in document order.
83
+ *
84
+ * Deliberately dumb — no knowledge of frames, no special cases. The moment this
85
+ * function needs to know what a table is, the invariant has stopped being an
86
+ * invariant.
87
+ */
88
+ export declare function projectedText(nodes: readonly PNode[]): string;
@@ -0,0 +1,63 @@
1
+ import type { PNode } from "./project";
2
+ /**
3
+ * Thrown when a patch would have had to disturb the node the browser is
4
+ * composing into.
5
+ *
6
+ * Loud rather than silent because the caller's contract is to DEFER the patch
7
+ * until `compositionend`, and a reconciler that quietly skipped the change
8
+ * would leave the DOM disagreeing with the model — which is the same corruption
9
+ * one step later, with nothing pointing at the cause.
10
+ */
11
+ export declare class CompositionConflictError extends Error {
12
+ constructor(message: string);
13
+ }
14
+ export interface PatchOptions {
15
+ /**
16
+ * The text node the browser is composing into. Never written, moved or
17
+ * removed; a patch that would need to do any of those throws.
18
+ */
19
+ protect?: Text | null;
20
+ }
21
+ export interface PatchStats {
22
+ /** Text nodes whose `data` was assigned. */
23
+ textUpdated: number;
24
+ /** Text nodes left alone because their data already matched. */
25
+ textUntouched: number;
26
+ /** Elements reused in place. */
27
+ elementsReused: number;
28
+ /** Nodes created, which is the number the caret can be disturbed by. */
29
+ created: number;
30
+ removed: number;
31
+ }
32
+ /**
33
+ * Brings `parent`'s children into agreement with `nodes`.
34
+ *
35
+ * Positional rather than keyed, deliberately. A keyed diff by source offset
36
+ * looks more sophisticated and is worse here: every offset after an edit shifts
37
+ * by the length of that edit, so keys change for the whole rest of the document
38
+ * on every keystroke and the diff replaces everything the user has not touched.
39
+ * Position is stable under exactly the edits that matter — a change in the
40
+ * middle of a paragraph leaves the paragraph at the same index.
41
+ */
42
+ export declare function patch(parent: HTMLElement, nodes: readonly PNode[], options?: PatchOptions): PatchStats;
43
+ /**
44
+ * The source range a text node carries, or null if it was not built here.
45
+ *
46
+ * The range is stamped on the node rather than looked up in a side table on
47
+ * purpose: a WeakMap keyed by node is the same information one indirection
48
+ * away, and the indirection is where a stale entry hides. A node that has no
49
+ * stamp is a node the browser created — which is a real event during
50
+ * composition, and the caller must treat it as one rather than guess.
51
+ */
52
+ export declare function rangeOf(node: Node): {
53
+ start: number;
54
+ end: number;
55
+ } | null;
56
+ /**
57
+ * Reads every text node under `root` in document order and joins it.
58
+ *
59
+ * This is the projection invariant, asserted against the REAL DOM rather than
60
+ * against the projection tree — which is a different claim and the one that
61
+ * actually matters. The tree can be right while the reconciler drops a node.
62
+ */
63
+ export declare function domText(root: Node): string;
@@ -0,0 +1,177 @@
1
+ import { type CaretMap } from "./caret-map";
2
+ import { type Projection, type ProjectOptions } from "./project";
3
+ import { type SourceSelection } from "./position";
4
+ import { type EditKind, type HistoryOptions } from "./history";
5
+ export interface SurfaceOptions {
6
+ /** Called after every committed change, with the new source. */
7
+ onChange?: (source: string) => void;
8
+ /** Undo tuning. The defaults are the constants the mature editors landed on. */
9
+ history?: HistoryOptions;
10
+ /**
11
+ * Highlights a fenced block. Injected, never imported: reaching for the
12
+ * grammar registry from the pure layer would make a projection depend on
13
+ * which languages happened to be registered when it ran. The component
14
+ * passes the real tokenizer; a test can pass none and get plain text.
15
+ */
16
+ highlight?: ProjectOptions["highlight"];
17
+ /**
18
+ * Called when the DOM stopped agreeing with the model and could not be
19
+ * repaired from it. Loud by default: this is the one failure that silently
20
+ * eats the user's text if it is swallowed.
21
+ */
22
+ onDesync?: (detail: string) => void;
23
+ }
24
+ export interface Splice {
25
+ start: number;
26
+ end: number;
27
+ text: string;
28
+ }
29
+ export declare class MarkdownSurface {
30
+ readonly root: HTMLElement;
31
+ private source;
32
+ private readonly options;
33
+ /** The text node the browser is composing into, if any. */
34
+ private composing;
35
+ /** A projection that arrived while composing, applied at `compositionend`. */
36
+ private deferred;
37
+ private observer;
38
+ /** Depth of "this mutation is ours, ignore it". */
39
+ private writing;
40
+ private readonly history;
41
+ /** True while applying an undo, so the undo is not itself recorded. */
42
+ private restoring;
43
+ /**
44
+ * Where the caret is allowed to be, rebuilt with every projection.
45
+ *
46
+ * Held rather than recomputed per keystroke because every deletion and every
47
+ * arrow key needs it, and it is a pure function of the projection that was
48
+ * just built anyway. It is never mutated — a new projection replaces it
49
+ * whole, which is the same "nothing is kept in step so nothing can drift"
50
+ * argument the projection itself rests on.
51
+ */
52
+ private caretStops;
53
+ /**
54
+ * The projection those stops were built from.
55
+ *
56
+ * Held for the same reason and handed to callers rather than re-derived: a
57
+ * deletion has to know which characters are markup and which construct owns
58
+ * them, and re-projecting to ask would parse the document twice per keypress.
59
+ */
60
+ private projection;
61
+ constructor(root: HTMLElement, source: string, options?: SurfaceOptions);
62
+ getSource(): string;
63
+ /** The projection the DOM currently shows, for callers that must read it. */
64
+ getProjection(): Projection;
65
+ /** Where the caret may legally be, for callers that have to reason about it. */
66
+ getCaretMap(): CaretMap;
67
+ /**
68
+ * The selection, snapped to positions that exist.
69
+ *
70
+ * SNAPPED RATHER THAN RETURNED RAW, because a click can land anywhere — the
71
+ * browser will happily put the caret at the edge of a zero-width marker, and
72
+ * every offset inside markup is now a place with no geometry. Normalising on
73
+ * the way IN means no caller downstream has to remember to.
74
+ */
75
+ getSelection(): SourceSelection | null;
76
+ /**
77
+ * Applies one splice and puts the caret where the inserted text ends.
78
+ *
79
+ * The caret is computed from the MODEL, never preserved from the DOM. That is
80
+ * the whole point of the design and also why it must be tested with a real
81
+ * caret in a real browser rather than asserted: a model-derived caret is
82
+ * exactly right or exactly wrong, with nothing in between.
83
+ */
84
+ apply(splice: Splice, select?: SourceSelection, kind?: EditKind): void;
85
+ /** Replaces the whole document, e.g. when the consumer's value prop changes. */
86
+ setSource(source: string): void;
87
+ get canUndo(): boolean;
88
+ get canRedo(): boolean;
89
+ undo(): boolean;
90
+ redo(): boolean;
91
+ /**
92
+ * Ends the current undo group.
93
+ *
94
+ * Called when the user moves the caret deliberately, because an edit after a
95
+ * selection change is a different thought however soon it arrives — one of
96
+ * the three coalescing rules, and the one a timer alone cannot express.
97
+ */
98
+ breakUndoGroup(): void;
99
+ private restore;
100
+ /**
101
+ * Re-projects and patches, then restores the caret.
102
+ *
103
+ * Refuses outright while composing. The deferral is recorded rather than
104
+ * dropped, so `compositionend` knows it owes a render — a skipped render that
105
+ * nobody remembers is a DOM that disagrees with the model forever.
106
+ */
107
+ private render;
108
+ /**
109
+ * Puts the caret back on a legal position after the USER moved it.
110
+ *
111
+ * A click, a drag or a vertical arrow key can leave the browser's selection
112
+ * at the edge of a collapsed marker, which is a place with no geometry and no
113
+ * meaning. Snapping costs one write and no re-projection — re-rendering here
114
+ * would be wasted work AND a risk, since every patch is a chance to disturb a
115
+ * caret that was already where it belonged.
116
+ *
117
+ * Replaces `refreshActive`, which existed only to move the reveal.
118
+ */
119
+ snapCaret(): void;
120
+ /**
121
+ * Moves the caret one VISIBLE character, in the model, and writes the result.
122
+ *
123
+ * Returns false when there is no selection to move, so the caller can let the
124
+ * browser have the key rather than swallowing it.
125
+ *
126
+ * One press crosses a whole marker, however many characters it holds, because
127
+ * the positions inside it are not positions. That is the difference between
128
+ * this and the grapheme step it replaces: `stepOffset` counted source
129
+ * characters, and under a contract where markup is never shown, counting
130
+ * source characters makes the caret appear to stall.
131
+ */
132
+ moveCaret(direction: 1 | -1, extend: boolean): boolean;
133
+ /**
134
+ * Detects the third caret rule's failure: our own write left the browser's
135
+ * selection somewhere that is no longer a position in this document.
136
+ *
137
+ * Both mature codebases carry a detector for this because Chrome does it, and
138
+ * because the symptom — a caret that has silently moved to the top of the
139
+ * document — is indistinguishable from a bug in the edit itself.
140
+ */
141
+ private verifySelection;
142
+ /**
143
+ * Binds the composition lifecycle. Returns the unbind function.
144
+ *
145
+ * `compositionstart` captures the node the browser will mutate — read from
146
+ * the live selection, because that is the only place the browser says which
147
+ * one it chose.
148
+ */
149
+ bind(): () => void;
150
+ /**
151
+ * Reads a finished composition back into the model.
152
+ *
153
+ * A composition is confined to ONE text node inside one inline element, and
154
+ * that node carries the source range it came from — so reconciling is
155
+ * "replace source characters a..b with this node's current text". A handful
156
+ * of lines against a known range, rather than the 407-line document-wide
157
+ * DOM-to-markdown serialiser this design exists to avoid.
158
+ */
159
+ private reconcileComposed;
160
+ /**
161
+ * Everything `preventDefault` could not cancel arrives here.
162
+ *
163
+ * This path is not a safety net, it is a REQUIREMENT: Chrome on Android
164
+ * reports `cancelable: true` on `deleteContentBackward` and deletes anyway,
165
+ * `insertCompositionText` is non-cancelable by spec, and iOS dictation fires
166
+ * no composition events at all. An editor with no observation path is simply
167
+ * broken on those inputs, and cannot be told that it is.
168
+ *
169
+ * The repair is deliberately narrow: a character-data change to a stamped
170
+ * node is read back into its range. Anything structural means the browser
171
+ * built nodes we cannot map, and the honest response is to re-project from
172
+ * the model we still trust, not to guess at what it did.
173
+ */
174
+ private onMutations;
175
+ /** Runs `fn` with our own mutations invisible to the observer. */
176
+ private write;
177
+ }
@@ -0,0 +1,40 @@
1
+ export declare function storageKey(name: string): string;
2
+ /**
3
+ * Read and parse, returning undefined for every failure mode.
4
+ *
5
+ * localStorage throws rather than returning null in two real situations —
6
+ * Safari private browsing, and any embedding where storage is blocked by
7
+ * policy. An unhandled throw here happens during the useState initializer,
8
+ * which unmounts the tree: the whole app disappears because a panel wanted to
9
+ * remember its width. Persistence is a convenience and must degrade to "use
10
+ * the default", never to a blank page.
11
+ */
12
+ export declare function readStored<T>(name: string, isValid: (value: unknown) => value is T): T | undefined;
13
+ export declare function writeStored(name: string, value: unknown): void;
14
+ export declare function clearStored(name: string): void;
15
+ /** Accepts anything — for callers whose stored shape needs no narrowing. */
16
+ export declare const anyValue: (value: unknown) => value is unknown;
17
+ export type PersistedStateOptions<T> = {
18
+ /**
19
+ * Rejects a stored value that no longer fits. REQUIRED thinking, not
20
+ * boilerplate: stored state outlives the code that wrote it, so a layout
21
+ * saved against three panels will be read back by a two-panel group after a
22
+ * refactor. Spreading it produces a broken layout that persists forever
23
+ * because it keeps being written back. Returning false throws it away and
24
+ * falls back to the default — the only recovery a user can't perform for
25
+ * themselves.
26
+ */
27
+ isValid: (value: unknown) => value is T;
28
+ /** Set false to stop persisting without changing any call site. */
29
+ enabled?: boolean;
30
+ };
31
+ /**
32
+ * `useState`, plus localStorage.
33
+ *
34
+ * The stored value is read in the INITIALIZER, not in an effect. Reading it in
35
+ * an effect paints the default first and then jumps — the panel visibly snaps
36
+ * from 50% to the 30% you left it at, on every single load. That flash is the
37
+ * entire reason people call persisted layouts janky, and it is avoidable by
38
+ * reading one frame earlier.
39
+ */
40
+ export declare function usePersistedState<T>(name: string | undefined, initial: T | (() => T), { isValid, enabled }: PersistedStateOptions<T>): readonly [T, import("react").Dispatch<import("react").SetStateAction<T>>, () => void];
@@ -0,0 +1,80 @@
1
+ /**
2
+ * Document-level display preferences.
3
+ *
4
+ * All three of theme, size scale and motion work the same way: a value lives on
5
+ * `<html data-*>`, CSS reads it, and localStorage remembers it. Writing that
6
+ * three times would be three chances to diverge — so it is written once and
7
+ * specialised below.
8
+ *
9
+ * WHY `<html>` AND NOT REACT CONTEXT: the value has to reach plain CSS, not
10
+ * just components. Portalled content (dialogs, popovers, toasts) renders
11
+ * outside the React tree that would hold the context, and a preference that
12
+ * fails on exactly the overlay layers is worse than no preference at all.
13
+ */
14
+ /**
15
+ * Writes one preference onto `<html>` with document-wide motion suspended for
16
+ * the duration of the change.
17
+ *
18
+ * WHY THIS IS NOT JUST `setAttribute`. Colour transitions are a foundation in
19
+ * this system: every element has one. That is right for an interaction — a
20
+ * control easing as you hover it — and wrong for a preference, because
21
+ * switching theme changes the computed colour of EVERY element at once.
22
+ * Measured on this demo: 2,360 transitions started simultaneously and the page
23
+ * took **957ms** to settle, 1,560ms on the chat route, for a token whose
24
+ * duration is 110ms. A theme switch should be a cut, not a dissolve; what it
25
+ * actually looked like was the whole document struggling.
26
+ *
27
+ * `--motion-scale: 0` is how it is suspended, rather than a blanket
28
+ * `transition: none !important`. Every duration in the system is already
29
+ * pre-multiplied by that scalar, so zeroing it turns them all off through the
30
+ * layer that exists for the purpose, overrides nothing a component declared,
31
+ * and cannot be forgotten by a component author.
32
+ *
33
+ * THE FORCED READ IS LOAD-BEARING. Style is normally recalculated once, after
34
+ * this function has returned — by which time the attribute is gone and the
35
+ * durations are back. Reading a computed value flushes the recalculation
36
+ * synchronously, so the new colours are committed WHILE durations are still
37
+ * zero, and no transition is ever started. Delete that line and the suspension
38
+ * silently does nothing at all, which is why `audit:motion` measures the settle
39
+ * time rather than the presence of the attribute.
40
+ */
41
+ export declare function applyPreference(attribute: string, value: string): void;
42
+ export type Theme = "light" | "dark";
43
+ export declare function useTheme(): {
44
+ theme: Theme;
45
+ setTheme: import("react").Dispatch<import("react").SetStateAction<Theme>>;
46
+ toggle: () => void;
47
+ };
48
+ export type UiScale = "sm" | "md" | "lg";
49
+ export declare function useUiScale(): {
50
+ scale: UiScale;
51
+ setScale: import("react").Dispatch<import("react").SetStateAction<UiScale>>;
52
+ cycle: () => void;
53
+ };
54
+ /**
55
+ * `design` is the palette as drawn; `aa` raises every token to the 4.5:1 floor.
56
+ *
57
+ * Opt-IN, not enforced, because enforcing the floor everywhere flattens the
58
+ * hierarchy for everyone: de-emphasised text stops being de-emphasised, and in
59
+ * this palette light-theme tertiary collapsed onto the same ramp step as
60
+ * secondary. A floor is a floor — reachable in one click by anyone who needs
61
+ * it, not a target that drags the whole design up to meet it.
62
+ */
63
+ export type ContrastPreference = "design" | "aa";
64
+ export declare function useContrast(): {
65
+ contrast: ContrastPreference;
66
+ setContrast: import("react").Dispatch<import("react").SetStateAction<ContrastPreference>>;
67
+ toggle: () => void;
68
+ };
69
+ /**
70
+ * `system` is a real third state, not a synonym for "full": it means defer to
71
+ * the OS, which is what most people want and what the OS-level preference
72
+ * exists to express. Collapsing it into a boolean would make an app-level
73
+ * control that says "full" override a user who set reduced motion system-wide.
74
+ */
75
+ export type MotionPreference = "system" | "full" | "reduced";
76
+ export declare function useMotionPreference(): {
77
+ motion: MotionPreference;
78
+ setMotion: import("react").Dispatch<import("react").SetStateAction<MotionPreference>>;
79
+ cycle: () => void;
80
+ };
@@ -0,0 +1,18 @@
1
+ import type { SimNamespace } from "./types";
2
+ export interface SimStoreOptions {
3
+ /** @default "demo" */
4
+ namespace?: SimNamespace;
5
+ }
6
+ export interface SimStore<T> {
7
+ /** Reactive — re-renders on ANY write to this store, anywhere. */
8
+ useAll(): T[];
9
+ useById(id: string): T | undefined;
10
+ /** Non-reactive escape hatch — reading outside a component, e.g. a seeder. */
11
+ getAll(): T[];
12
+ create(item: T): Promise<T>;
13
+ update(id: string, patch: Partial<T>): Promise<T>;
14
+ remove(id: string): Promise<void>;
15
+ /** Back to `seed`. What "Regenerate demo data" calls, via `resetSimStores`. */
16
+ reset(): Promise<void>;
17
+ }
18
+ export declare function createSimStore<T>(key: string, seed: T[], getId: (item: T) => string, options?: SimStoreOptions): SimStore<T>;
@@ -0,0 +1,5 @@
1
+ export * from "./types";
2
+ export * from "./createSimStore";
3
+ export * from "./registry";
4
+ export * from "./settings";
5
+ export * from "./latency";