@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,132 @@
1
+ import type { DragSession } from "./session";
2
+ import type { ContainerId, DragId } from "./types";
3
+ /** Everything a message may name. Assembled once per announcement. */
4
+ export interface DragAnnouncementContext {
5
+ id: DragId;
6
+ /** `labelOf(id)` if supplied, otherwise the id — which is at least speakable. */
7
+ label: string;
8
+ container: ContainerId;
9
+ containerLabel: string;
10
+ /** 1-based, as spoken. */
11
+ position: number;
12
+ /**
13
+ * The list length WITH the carried item counted in it, so `position` is never
14
+ * "4 of 3". The session's own indices exclude the carried item — see the
15
+ * `DropTarget.index` contract — and reporting that raw count would tell the
16
+ * user a list is one shorter than the one they are looking at.
17
+ */
18
+ total: number;
19
+ }
20
+ export interface DragStrings {
21
+ /** Rendered by `<DragInstructions>`; referenced by each handle's `aria-describedby`. */
22
+ instructions: string;
23
+ picked: (c: DragAnnouncementContext) => string;
24
+ moved: (c: DragAnnouncementContext) => string;
25
+ dropped: (c: DragAnnouncementContext) => string;
26
+ /** Describes the item's ORIGINAL position — cancel is a restore, not a move. */
27
+ cancelled: (c: DragAnnouncementContext) => string;
28
+ }
29
+ /**
30
+ * Exported as data so a consumer can read one message, override one message, or
31
+ * assert on the set — a grammar hidden inside the component that renders it can
32
+ * only be checked by rendering it.
33
+ */
34
+ export declare const DEFAULT_DRAG_STRINGS: DragStrings;
35
+ export interface DragAnnouncementOptions {
36
+ /** Human name for an item. Falls back to the id. */
37
+ labelOf?: (id: DragId) => string;
38
+ /** Human name for a container. Falls back to the id. */
39
+ containerLabelOf?: (id: ContainerId) => string;
40
+ /** Override any subset of the grammar — localisation, or a domain noun. */
41
+ strings?: Partial<DragStrings>;
42
+ }
43
+ /**
44
+ * One id for the whole page, deliberately not `useId()`.
45
+ *
46
+ * `<DragInstructions>` takes no required props and the hook hands the id to
47
+ * every handle, so the two can only agree on a CONSTANT — a generated id would
48
+ * differ per hook instance and every `aria-describedby` would point at nothing,
49
+ * which reads as "no instructions" and is invisible to every automated check.
50
+ * The cost: render `<DragInstructions>` once per page, not once per board.
51
+ */
52
+ export declare const DRAG_INSTRUCTIONS_ID = "danxbot-dnd-instructions";
53
+ export interface DragAnnouncements {
54
+ /** The current live-region text. `""` until the first pickup. */
55
+ message: string;
56
+ /** Put this on every handle's `aria-describedby`. */
57
+ instructionsId: string;
58
+ }
59
+ /**
60
+ * Subscribe to the session and turn it into speech.
61
+ *
62
+ * AN OBSERVER, NOT A STATE SUBSCRIPTION. The messages are derived from
63
+ * TRANSITIONS, not from state, and the two that matter most are indistinguishable
64
+ * in state: a drop and a cancel both end at the same idle value, so a subscriber
65
+ * sees one identical transition for two endings that need opposite messages — a
66
+ * drop reports where the item LANDED, a cancel where it went BACK to.
67
+ *
68
+ * `session.addObserver` exists for exactly this. It was added after the first
69
+ * version of this file inferred the ending from whichever DOM event was
70
+ * mid-dispatch, because the session's callbacks were a single slot the board
71
+ * already owned. That worked, and was ninety lines, and was defeated once by the
72
+ * microtask checkpoint the HTML spec runs BETWEEN listeners of the same event —
73
+ * so every cancel announced itself as a drop, in a real browser only. Nothing
74
+ * that reads the session should have to guess what the session just did.
75
+ *
76
+ * The context for a message is captured WHILE THE DRAG IS LIVE, never at the
77
+ * end. "Position 3 of 7" needs the carried item excluded from its own total, and
78
+ * by the time the drag has ended the session no longer knows which item that
79
+ * was — the number would quietly gain one.
80
+ */
81
+ export declare function useDragAnnouncements(session: DragSession, options?: DragAnnouncementOptions): DragAnnouncements;
82
+ export type DragLiveRegionProps = {
83
+ session: DragSession;
84
+ } & DragAnnouncementOptions;
85
+ /**
86
+ * The live region. Render it ONCE, unconditionally, for the whole board.
87
+ *
88
+ * UNCONDITIONALLY IS THE LOAD-BEARING WORD. A live region that is inserted and
89
+ * populated in the same frame — `{dragging && <DragLiveRegion/>}` — frequently
90
+ * announces nothing at all: the accessibility tree has to observe the region
91
+ * BEFORE its content changes to know a change happened. It renders empty here
92
+ * and stays mounted, which is also why the message lives in state rather than
93
+ * being derived from `session.getState()` during render.
94
+ *
95
+ * ASSERTIVE, NOT POLITE. Polite queues behind whatever is currently being
96
+ * spoken, and a drag generates positions faster than speech consumes them — so
97
+ * the user hears "position 2 of 7" while the card sits at 6, which is a wrong
98
+ * answer rather than a late one. Assertive interrupts, so the newest position
99
+ * always wins. The usual argument against it — that it talks over the user —
100
+ * does not apply to a gesture that is modal by construction: nothing else is
101
+ * happening, and the drag's own output is the only thing being interrupted.
102
+ *
103
+ * `sr-only` — NOT `display:none`, NOT `visibility:hidden`, NOT `hidden`. All
104
+ * three remove the element from the accessibility tree entirely, and a live
105
+ * region outside the accessibility tree is silent while looking exactly right
106
+ * in the markup.
107
+ */
108
+ export declare function DragLiveRegion({ session, ...options }: DragLiveRegionProps): import("react").JSX.Element;
109
+ export interface DragInstructionsProps {
110
+ strings?: Partial<DragStrings>;
111
+ /**
112
+ * The element id each handle's `aria-describedby` points at.
113
+ *
114
+ * Defaults to the module constant, which is what makes a prop-less
115
+ * `<DragInstructions>` and `useDragAnnouncements` agree without wiring. A
116
+ * component layer that generates its own id with `useId()` — so that two
117
+ * boards on one page do not emit a duplicate id — passes it here. Without
118
+ * this prop that layer has to re-implement the paragraph to change one
119
+ * attribute, which is how the instruction text ends up existing twice and
120
+ * drifting.
121
+ */
122
+ id?: string;
123
+ }
124
+ /**
125
+ * The instruction text every handle points at with `aria-describedby`.
126
+ *
127
+ * A keyboard drag is unusable without it: nothing about a focused handle says
128
+ * that Space starts a mode, and a user who does not know that never reaches any
129
+ * of the rest of this file. Visually hidden because the same information is
130
+ * carried for sighted users by the cursor and the moving card.
131
+ */
132
+ export declare function DragInstructions({ strings, id }?: DragInstructionsProps): import("react").JSX.Element;
@@ -0,0 +1,88 @@
1
+ import type { DragSession } from "./session";
2
+ import type { Point } from "./types";
3
+ /**
4
+ * Edge auto-scroll: while a card is being carried, a pointer near the edge of
5
+ * the nearest scrollable ancestor scrolls it.
6
+ *
7
+ * WHY IT IS NOT OPTIONAL. Without it a list is only reorderable within one
8
+ * screenful. The target you want is off-screen, the gesture is already in
9
+ * flight, and there is no way to scroll with the hand that is holding the card
10
+ * — so the drag has to be abandoned, the list scrolled, and the drag restarted
11
+ * from a position that may itself now be off-screen.
12
+ *
13
+ * THREE THINGS THAT MAKE IT WORK RATHER THAN MERELY EXIST:
14
+ *
15
+ * 1. SPEED RAMPS with proximity. A single speed is unusable at both ends: fast
16
+ * enough to cross a long column overshoots the moment you approach the
17
+ * edge, slow enough to be controllable never gets anywhere. The ramp is
18
+ * quadratic, so the outer half of the zone crawls and the last few pixels
19
+ * are quick.
20
+ * 2. IT RE-MEASURES. Every rect in the snapshot moves when the container
21
+ * scrolls. Without a remeasure the insertion point keeps pointing at where
22
+ * the items USED to be, so the marker sits still while the list slides past
23
+ * it and the card lands somewhere the user never indicated.
24
+ * 3. IT RE-RESOLVES THE TARGET. Re-measuring alone updates the boxes but not
25
+ * the answer: the pointer has not moved, so nothing recomputes `over`, and
26
+ * the marker stays frozen on the old index against fresh rects. Feeding the
27
+ * unchanged point back through `moveTo` is what makes the insertion point
28
+ * track the content moving beneath a stationary finger.
29
+ *
30
+ * The window `scroll` listener in `sensors/pointer.ts` also remeasures, and is
31
+ * NOT a substitute: scroll events are coalesced and delivered after the frame
32
+ * has been composited, so the hit-test for the frame we just scrolled would
33
+ * still read the pre-scroll snapshot. This loop does it inline, before the next
34
+ * frame is resolved.
35
+ */
36
+ export interface AutoScrollOptions {
37
+ /** Distance from an edge, in px, at which scrolling begins. */
38
+ threshold?: number;
39
+ /** Speed in px/second at the very edge, at full motion. */
40
+ maxSpeed?: number;
41
+ /** Speed in px/second at the very edge under reduced motion. See D-18. */
42
+ reducedMaxSpeed?: number;
43
+ }
44
+ /**
45
+ * Speed at a given distance from the edge. Exported because it is the whole
46
+ * behaviour and it is the one part testable without a browser.
47
+ *
48
+ * Zero outside the zone, `maxSpeed` at the edge, quadratic in between.
49
+ */
50
+ export declare function scrollVelocity(distanceFromEdge: number, threshold: number, maxSpeed: number): number;
51
+ /**
52
+ * Start auto-scrolling for the lifetime of every drag on this session.
53
+ * Returns the teardown.
54
+ *
55
+ * Framework-free on purpose, like the rest of the core — a React consumer wraps
56
+ * it in one `useEffect`, and the tests do not need a renderer to drive it.
57
+ */
58
+ /**
59
+ * What the loop needs from whoever is dragging.
60
+ *
61
+ * EXTRACTED SO THE CALENDAR CAN USE IT UNCHANGED. Every hard-won detail below —
62
+ * the carried sub-pixel remainder, the clamped frame delta, the per-scroller
63
+ * reset, the reduced-motion cap — is about SCROLLING, not about lists, and a
64
+ * second copy of it in the calendar would be a second copy of the one thing
65
+ * whose failures are invisible (a ramp that silently loses its outer half is
66
+ * not something anyone notices; it just feels bad).
67
+ */
68
+ export interface AutoScrollDriver {
69
+ /** The pointer, or null when the gesture has no pointer (a keyboard drag). */
70
+ getPoint: () => Point | null;
71
+ /**
72
+ * The content moved under a pointer that did not.
73
+ *
74
+ * RE-FEEDING THE POINT IS THE WHOLE REASON THIS CALLBACK EXISTS. Remeasuring
75
+ * alone updates the boxes and not the ANSWER: the pointer has not moved, so
76
+ * nothing recomputes what it is over, and the drag freezes mid-scroll.
77
+ * FullCalendar solves the same problem by re-emitting a synthetic
78
+ * `pointermove` on scroll — the same fix arrived at from the other side.
79
+ */
80
+ onScrolled: (point: Point) => void;
81
+ }
82
+ /** The loop, driven by anything that can answer those two questions. */
83
+ export declare function attachEdgeAutoScroll(driver: AutoScrollDriver, options?: AutoScrollOptions): () => void;
84
+ /**
85
+ * The list engine's adapter: drive the edge loop from a `DragSession`, and add
86
+ * the keyboard-only behaviour the pointer ramp cannot reach.
87
+ */
88
+ export declare function attachAutoScroll(session: DragSession, options?: AutoScrollOptions): () => void;
@@ -0,0 +1,78 @@
1
+ import type { ContainerRect, ContainerRegistration, DragId, DropTarget, ItemRect, ItemRegistration, LayoutSnapshot, Point } from "./types";
2
+ /**
3
+ * Measuring and hit-testing. Pure functions over a snapshot — no DOM reads
4
+ * except in `measure`, and no state at all, so every rule here is testable
5
+ * against hand-written rectangles rather than a browser.
6
+ */
7
+ /**
8
+ * Take every box at once.
9
+ *
10
+ * Items are emitted in container-then-index order so `resolveTarget` can walk
11
+ * them without re-sorting on every move. Registration order is whatever React
12
+ * mounted in, which is not the reading order once a list re-renders.
13
+ */
14
+ export declare function measure(items: Iterable<ItemRegistration>, containers: Iterable<ContainerRegistration>): LayoutSnapshot;
15
+ /**
16
+ * The container under the pointer.
17
+ *
18
+ * SMALLEST AREA WINS, because containers nest: a board is a horizontal
19
+ * container of columns, and each column is a vertical container of cards. The
20
+ * pointer is inside both at all times, and the answer that is useful is always
21
+ * the innermost one.
22
+ */
23
+ export declare function containerAt(snapshot: LayoutSnapshot, point: Point): ContainerRect | null;
24
+ /** A container's items in reading order, excluding the one being carried. */
25
+ export declare function remainingItems(snapshot: LayoutSnapshot, container: string, dragged: DragId | null): ItemRect[];
26
+ /**
27
+ * Insertion index for a point within one container.
28
+ *
29
+ * THE MIDPOINT RULE: insert before the first remaining item whose centre lies
30
+ * beyond the pointer on the container's axis; after all of them otherwise. This
31
+ * is the rule the dashboard's implementation already uses and it is the right
32
+ * one for lists — the insertion point tracks the cursor rather than the item's
33
+ * leading edge, so a card released halfway over a neighbour lands on the side
34
+ * the user is actually pointing at.
35
+ *
36
+ * The returned index counts the list WITHOUT the dragged item, which is what
37
+ * makes same-container and cross-container drops identical arithmetic. See the
38
+ * header of `types.ts`.
39
+ */
40
+ export declare function indexAt(snapshot: LayoutSnapshot, container: ContainerRect, point: Point, dragged: DragId | null): number;
41
+ /**
42
+ * Where the dragged item would land if released at `point`.
43
+ *
44
+ * Returns null when the pointer is over no container at all. The session keeps
45
+ * the PREVIOUS target in that case rather than clearing it — dragging a card
46
+ * briefly outside the board should not make the insertion marker vanish, and a
47
+ * release outside every container should still land where the marker last
48
+ * showed. That is the behaviour every board people already use has, and the
49
+ * alternative reads as the drag having silently failed.
50
+ */
51
+ export declare function resolveTarget(snapshot: LayoutSnapshot, point: Point, dragged: DragId | null): DropTarget | null;
52
+ /** Value equality — targets are recreated on every move, so `===` is useless. */
53
+ export declare function sameTarget(a: DropTarget | null, b: DropTarget | null): boolean;
54
+ /**
55
+ * Would releasing at `to` leave the item exactly where it started?
56
+ *
57
+ * Worth stating rather than inlining, because the two indices are counted
58
+ * against DIFFERENT lists: `from.index` is the item's position with itself
59
+ * present, `to.index` an insertion point with itself removed. They coincide at
60
+ * exactly one value. For `[A, B, C, D]` carrying `C` (`from.index` 2), the
61
+ * remaining list is `[A, B, D]` and inserting at 2 rebuilds `[A, B, C, D]` —
62
+ * so equality of the two numbers IS the no-op, and every other pair moves
63
+ * something.
64
+ *
65
+ * A consumer that skips this writes on every release, including the ones that
66
+ * change nothing. That is not free: the dashboard's board reordered by writing
67
+ * a priority decimal, so a no-op release still cost a request, a re-render and
68
+ * a row in the audit log.
69
+ */
70
+ export declare function isNoOp(from: DropTarget, to: DropTarget): boolean;
71
+ /**
72
+ * Convert a `DropTarget` into the array operation a consumer performs.
73
+ *
74
+ * Exported because every consumer needs it and each one getting it subtly wrong
75
+ * is the entire off-by-one bug class this module exists to remove. Returns a
76
+ * NEW array; the input is untouched.
77
+ */
78
+ export declare function applyMove<T>(list: readonly T[], fromIndex: number, toIndex: number): T[];
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Drag-and-drop engine. Framework-agnostic core plus a React binding.
3
+ *
4
+ * Exported as VALUES from one place — never `import "./x"` for a side effect.
5
+ * `sideEffects: ["*.css"]` tells every bundler such an import is safe to
6
+ * delete, and esbuild does; that is exactly how this project once shipped an
7
+ * empty icon registry (CAV-11).
8
+ */
9
+ export type { ContainerId, ContainerRect, ContainerRegistration, DragAxis, DragCancelEvent, DragEndEvent, DragId, DragMode, DragPhase, DragSessionCallbacks, DragStartEvent, DragState, DropTarget, ItemRect, ItemRegistration, LayoutSnapshot, Point, } from "./types";
10
+ export { applyMove, containerAt, indexAt, isNoOp, measure, remainingItems, resolveTarget, sameTarget, } from "./geometry";
11
+ export { DragSession, type StartArgs } from "./session";
12
+ export { attachPointerSensor, attachSessionWindowListeners, type PointerActivation, type PointerSensorOptions, } from "./sensors/pointer";
13
+ export { attachKeyboardSensor, type KeyboardSensorOptions, } from "./sensors/keyboard";
14
+ export { attachAutoScroll, scrollVelocity, type AutoScrollOptions, } from "./autoscroll";
15
+ export { DragSessionProvider, useCreateDragSession, useDragSession, useDragState, useDraggable, useDropContainer, type UseDraggableOptions, type UseDraggableResult, type UseDropContainerOptions, type UseDropContainerResult, } from "./use-drag";
16
+ export { DEFAULT_DRAG_STRINGS, DRAG_INSTRUCTIONS_ID, DragInstructions, DragLiveRegion, useDragAnnouncements, type DragAnnouncementContext, type DragAnnouncementOptions, type DragAnnouncements, type DragInstructionsProps, type DragLiveRegionProps, type DragStrings, } from "./announce";
@@ -0,0 +1,37 @@
1
+ import type { Point } from "../types";
2
+ export interface PointerActivation {
3
+ /** Mouse and pen: travel in px before the drag starts. */
4
+ distance?: number;
5
+ /** Touch: press duration in ms before the drag starts. */
6
+ delay?: number;
7
+ /** Touch: travel in px that abandons the press as a scroll instead. */
8
+ tolerance?: number;
9
+ }
10
+ export declare const ACTIVATION_DEFAULTS: Required<PointerActivation>;
11
+ export interface GestureHandlers {
12
+ /**
13
+ * The pointer went down. Return false to refuse the gesture outright — a
14
+ * disabled row, a click on a button inside the card.
15
+ */
16
+ onPress?: (point: Point, event: PointerEvent) => boolean | void;
17
+ /** The activation constraint was satisfied. The drag is now live. */
18
+ onActivate: (point: Point) => void;
19
+ /** Only ever called between `onActivate` and one of the two enders. */
20
+ onMove: (point: Point) => void;
21
+ /** Released. `activated` distinguishes a drop from a plain click. */
22
+ onRelease: (point: Point, activated: boolean) => void;
23
+ /** The gesture was abandoned — a `pointercancel`, or a touch that scrolled. */
24
+ onCancel: () => void;
25
+ }
26
+ export interface GestureOptions {
27
+ activation?: PointerActivation;
28
+ }
29
+ /**
30
+ * Wire an element as a drag handle. Returns the teardown.
31
+ *
32
+ * The listeners live on the ELEMENT, not the document, and the element takes
33
+ * pointer capture at `pointerdown` — so every subsequent move and the release
34
+ * are delivered here even when the pointer leaves the element, which it does
35
+ * immediately and for the entire gesture.
36
+ */
37
+ export declare function attachPointerGesture(element: HTMLElement, handlers: GestureHandlers, options?: GestureOptions): () => void;
@@ -0,0 +1,50 @@
1
+ import type { DragSession } from "../session";
2
+ import type { DragAxis, DragId } from "../types";
3
+ /**
4
+ * The keyboard sensor. Drives the SAME session the pointer does.
5
+ *
6
+ * WHY A SENSOR AND NOT A KEYBOARD IMPLEMENTATION. The usual shape is a pointer
7
+ * drag with keyboard "support" bolted on, which is two codebases computing the
8
+ * same answer — and the accessible one is the one nobody looks at, so it is the
9
+ * one that rots. Here the keyboard can only `start`, `moveIndex`,
10
+ * `moveContainer`, `commit` and `cancel`, exactly like the pointer, so the two
11
+ * cannot disagree about where a card lands. `dnd-sensors.test.ts` asserts they
12
+ * reach an identical target for the same intent.
13
+ *
14
+ * THE KEY MAP IS FIXED BY THE PROJECT CONTRACT, not by this file:
15
+ *
16
+ * Space / Enter pick up on a focused handle; drop when already carrying
17
+ * Escape cancel — handled by `attachSessionWindowListeners`, NOT here
18
+ * Along the axis step the insertion point inside the current container
19
+ * Across it move to the previous / next container
20
+ *
21
+ * "Along" and "across" swap with the container's axis: a vertical list steps
22
+ * with Up/Down and changes column with Left/Right; a horizontal one is the
23
+ * mirror. Hard-coding Up/Down as "within" makes a horizontal list unusable in
24
+ * the one input mode that has no alternative.
25
+ *
26
+ * NO `aria-grabbed`, NO `aria-dropeffect`. They are the attributes the name
27
+ * suggests and they were removed in ARIA 1.2 because assistive-technology
28
+ * support never arrived — an implementation using them announces nothing at
29
+ * all. Ordinary focus, an `aria-describedby` instruction and a live region are
30
+ * the working pattern; the live region is `announce.tsx`'s.
31
+ */
32
+ export interface KeyboardSensorOptions {
33
+ id: DragId;
34
+ /**
35
+ * The reorder axis of the container this item currently lives in. Decides
36
+ * which arrow pair steps within the container and which moves between them.
37
+ */
38
+ axis?: DragAxis;
39
+ /** Return false to refuse pickup — a disabled row. Mirrors the pointer sensor. */
40
+ canStart?: (e: KeyboardEvent) => boolean;
41
+ }
42
+ /**
43
+ * Wire an element as a keyboard drag handle. Returns the teardown.
44
+ *
45
+ * The element must be focusable and reachable — `tabIndex={0}` and
46
+ * `role="button"` — or none of this is ever delivered. A handle that is a
47
+ * `<span>` with a listener is invisible to the keyboard and to every screen
48
+ * reader, and looks completely correct on a desktop.
49
+ */
50
+ export declare function attachKeyboardSensor(session: DragSession, element: HTMLElement, options: KeyboardSensorOptions): () => void;
@@ -0,0 +1,59 @@
1
+ import type { DragSession } from "../session";
2
+ import type { DragId } from "../types";
3
+ import { type PointerActivation } from "./gesture";
4
+ /**
5
+ * The pointer sensor. One listener set for mouse, touch and pen.
6
+ *
7
+ * ACTIVATION IS THE WHOLE PROBLEM. Starting the drag on `pointerdown` is what
8
+ * makes a drag list unusable: a tap becomes a drag, and on a phone a swipe that
9
+ * was meant to scroll the column picks a card up instead. So a pointer is held
10
+ * in `pending` until it proves itself, and the proof differs by input type:
11
+ *
12
+ * - MOUSE and PEN have a cursor, so intent is expressed by MOVING. A few
13
+ * pixels of travel is unambiguous and instant, and a click never reaches it.
14
+ * - TOUCH has no cursor and movement is how you scroll, so movement cannot be
15
+ * the signal. Intent is expressed by DWELLING — the long press — and any
16
+ * travel beyond a small tolerance before the timer fires means the finger
17
+ * was scrolling and the session must be abandoned.
18
+ *
19
+ * Getting that backwards is the single most common defect in drag lists, and it
20
+ * only shows up on a device the author was not testing on.
21
+ *
22
+ * AND THE DWELL IS ONLY HALF OF THE TOUCH ANSWER. The activation constraint
23
+ * decides whether WE start a drag; it says nothing about whether the BROWSER
24
+ * starts a scroll, and the browser decides that from `touch-action` alone,
25
+ * before any of this code runs. `touch-action: none` on the handle is therefore
26
+ * the tempting fix and it re-creates the failure it was meant to prevent, just
27
+ * on a smaller target: every swipe that happens to begin on the grip is eaten,
28
+ * the column does not move, and the list feels stuck at random.
29
+ *
30
+ * The handle is `touch-action: pan-y` instead (`pan-x` for a horizontal list),
31
+ * so the browser scrolls exactly as it would on any other element, and the
32
+ * drag takes the gesture back at the moment it activates by cancelling
33
+ * `touchmove` — see `beginTouchScrollGuard`. That ordering is what makes both
34
+ * true on the SAME element: a swipe scrolls, a press picks up.
35
+ */
36
+ export type { PointerActivation } from "./gesture";
37
+ export interface PointerSensorOptions {
38
+ id: DragId;
39
+ activation?: PointerActivation;
40
+ /** Return false to refuse the gesture — a disabled row, or a click on a button inside the card. */
41
+ canStart?: (e: PointerEvent) => boolean;
42
+ }
43
+ /**
44
+ * Wire an element as a drag handle for a list `DragSession`.
45
+ *
46
+ * A THIN ADAPTER, deliberately. The session's four verbs map one-to-one onto
47
+ * the gesture's callbacks, and the only real logic left is the `pending`
48
+ * start — the session is told about the press immediately so it can refuse the
49
+ * item, and only activated once the constraint is met.
50
+ */
51
+ export declare function attachPointerSensor(session: DragSession, element: HTMLElement, options: PointerSensorOptions): () => void;
52
+ /**
53
+ * Esc, and the two things that move a box while a drag is in flight.
54
+ *
55
+ * Window-level because a keypress during a drag does not go to the handle — the
56
+ * handle has pointer capture, not focus — and because a scroll of an ancestor
57
+ * invalidates every rect in the snapshot at once.
58
+ */
59
+ export declare function attachSessionWindowListeners(session: DragSession): () => void;
@@ -0,0 +1,99 @@
1
+ import type { ContainerRegistration, DragId, DragMode, DragSessionCallbacks, DragState, ItemRegistration, LayoutSnapshot, Point } from "./types";
2
+ export interface StartArgs {
3
+ id: DragId;
4
+ mode: DragMode;
5
+ /** Pointer position at pickup. Omitted for keyboard sessions. */
6
+ point?: Point;
7
+ /**
8
+ * `true` for a pointer that is down but has not passed its activation
9
+ * constraint yet. The session holds `pending` until the sensor calls
10
+ * `activate()` — see `DragPhase` in `types.ts` for why that gap exists.
11
+ */
12
+ pending?: boolean;
13
+ }
14
+ export declare class DragSession {
15
+ private state;
16
+ private listeners;
17
+ private items;
18
+ private containers;
19
+ private layout;
20
+ private callbacks;
21
+ private observers;
22
+ constructor(callbacks?: DragSessionCallbacks);
23
+ /**
24
+ * The OWNING consumer's callbacks — the one that renders the board and writes
25
+ * the reorder. Replaced rather than passed per-call because a React consumer
26
+ * re-creates its handlers on every render and the session must not be rebuilt;
27
+ * rebuilding it mid-gesture drops the drag.
28
+ */
29
+ setCallbacks(callbacks: DragSessionCallbacks): void;
30
+ /**
31
+ * Register an ADDITIONAL listener. Returns its teardown.
32
+ *
33
+ * `setCallbacks` is a single slot owned by whoever renders the board, so a
34
+ * second interested party — the live-region announcer, an analytics hook, a
35
+ * test — could not be told anything without clobbering it. And `subscribe`
36
+ * is not a substitute: a drop and a cancel both end at the same idle state,
37
+ * so a state subscriber sees ONE identical transition for two outcomes that
38
+ * must be announced differently.
39
+ *
40
+ * That gap was not theoretical. The announcer worked around it by inferring
41
+ * the cause from whichever DOM event was mid-dispatch — about ninety lines of
42
+ * genuinely careful machinery, defeated once by the microtask checkpoint the
43
+ * HTML spec runs BETWEEN listeners of the same event, so that every cancel
44
+ * announced itself as a drop. Nothing that reads the session should have to
45
+ * guess what the session just did.
46
+ */
47
+ addObserver(callbacks: DragSessionCallbacks): () => void;
48
+ /**
49
+ * Owner first, then observers, in registration order.
50
+ *
51
+ * The owner goes first because it is the one that mutates the list, and an
52
+ * observer that reads the consumer's state — an announcer reporting "3 of 7"
53
+ * — must see the world after the write, not before.
54
+ */
55
+ private fan;
56
+ getState: () => DragState;
57
+ subscribe: (fn: () => void) => (() => void);
58
+ private emit;
59
+ registerItem(reg: ItemRegistration): () => void;
60
+ registerContainer(reg: ContainerRegistration): () => void;
61
+ /** Registered order, used by keyboard container stepping. */
62
+ containerIds(): string[];
63
+ /**
64
+ * Re-measure. Called at pickup, and by the sensors on scroll, resize and
65
+ * every auto-scroll frame — the three things that move a box while a drag is
66
+ * in flight without the drag having caused it.
67
+ */
68
+ remeasure(): LayoutSnapshot;
69
+ getLayout(): LayoutSnapshot | null;
70
+ start(args: StartArgs): boolean;
71
+ /** Promote a `pending` pointer into a real drag once its constraint is met. */
72
+ activate(): void;
73
+ /** Pointer moved. Cheap when the target has not changed, which is most frames. */
74
+ moveTo(point: Point): void;
75
+ /**
76
+ * Step the insertion point within the current container. The keyboard's
77
+ * along-axis movement.
78
+ *
79
+ * Clamped rather than wrapping: wrapping from the bottom of a list to the top
80
+ * is indistinguishable from a missed keypress when you cannot see the list.
81
+ */
82
+ moveIndex(delta: number): void;
83
+ /**
84
+ * Move to the previous or next container. The keyboard's cross-axis movement.
85
+ *
86
+ * Lands at the index nearest the item's current one rather than at the top,
87
+ * because a card carried sideways across a board should stay roughly where it
88
+ * was vertically — the same thing the pointer does for free.
89
+ */
90
+ moveContainer(delta: number): void;
91
+ /** Release. Emits `onDragEnd` even for a no-op so consumers can announce it. */
92
+ commit(): void;
93
+ /** Esc, `pointercancel`, or an unmount mid-gesture. Never writes. */
94
+ cancel(): void;
95
+ /** The container the pointer is over right now, or null. */
96
+ containerAtPoint(point: Point): string | null;
97
+ /** How many items a container holds once the carried one is taken out. */
98
+ slotCount(container: string): number;
99
+ }