@danxbot/ui 20.9.0 → 20.11.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.
- package/dist/index.js +11834 -11342
- package/dist/index.js.map +1 -1
- package/dist/types/components/Board.d.ts +10 -3
- package/dist/types/components/DragDrop.d.ts +10 -1
- package/dist/types/components/PagedWindowList.d.ts +31 -0
- package/dist/types/components/board/recipe.d.ts +3 -0
- package/dist/types/components/paged-window/PlaceholderBlock.d.ts +21 -0
- package/dist/types/index.d.ts +5 -3
- package/dist/types/lib/board/anchor.d.ts +37 -0
- package/dist/types/lib/board/index.d.ts +2 -1
- package/dist/types/lib/board/types.d.ts +47 -4
- package/dist/types/lib/dnd/announce.d.ts +1 -1
- package/dist/types/lib/dnd/index.d.ts +1 -1
- package/dist/types/lib/dnd/session.d.ts +12 -0
- package/dist/types/lib/dnd/types.d.ts +9 -0
- package/dist/types/lib/dnd/use-drag.d.ts +14 -0
- package/dist/types/lib/dom/scroll-ancestor.d.ts +15 -0
- package/dist/types/lib/paged-window/index.d.ts +2 -0
- package/dist/types/lib/paged-window/store.d.ts +139 -0
- package/dist/types/lib/paged-window/use-measured-pitch.d.ts +23 -0
- package/dist/types/lib/paged-window/use-page-visibility.d.ts +20 -0
- package/dist/types/lib/paged-window/use-paged-window.d.ts +41 -0
- package/dist/types/lib/paged-window/use-scroll-root.d.ts +18 -0
- package/package.json +2 -2
|
@@ -138,12 +138,19 @@ export interface BoardColumnProps<T> {
|
|
|
138
138
|
* never colour or glyph alone (WCAG 1.4.1).
|
|
139
139
|
*/
|
|
140
140
|
icon?: ReactNode;
|
|
141
|
+
/**
|
|
142
|
+
* DX-4540: controls drawn at the header's end, after the count — a
|
|
143
|
+
* `SortMenu`, say. A slot of content, not a boolean: supplying it is what
|
|
144
|
+
* adds it. It sits in the header, outside the scrolling list, so it stays
|
|
145
|
+
* put while the cards scroll.
|
|
146
|
+
*/
|
|
147
|
+
actions?: ReactNode;
|
|
141
148
|
/** The column's cards — a `BoardCard` per item, mapped by the consumer
|
|
142
|
-
* exactly as `DragDropContainer`'s own children already are.
|
|
143
|
-
*
|
|
149
|
+
* exactly as `DragDropContainer`'s own children already are. A windowed
|
|
150
|
+
* lane puts a `PagedWindowList` here — see the file header. */
|
|
144
151
|
children?: ReactNode;
|
|
145
152
|
}
|
|
146
|
-
export declare function BoardColumn<T>({ column, emptyState, className, listClassName, accent, icon, children, }: BoardColumnProps<T>): import("react").JSX.Element;
|
|
153
|
+
export declare function BoardColumn<T>({ column, emptyState, className, listClassName, accent, icon, actions, children, }: BoardColumnProps<T>): import("react").JSX.Element;
|
|
147
154
|
export interface BoardCardProps {
|
|
148
155
|
id: BoardItemId;
|
|
149
156
|
/** Position within its OWN column right now — same contract as
|
|
@@ -37,10 +37,19 @@ export interface DragDropContainerProps {
|
|
|
37
37
|
* @default "y"
|
|
38
38
|
*/
|
|
39
39
|
axis?: DragAxis;
|
|
40
|
+
/**
|
|
41
|
+
* DX-4540: for a container that renders only a window of a longer list — the
|
|
42
|
+
* position in the whole list of its first item. Every announcement then
|
|
43
|
+
* speaks the real place ("position 221 of 500"), not the place among what
|
|
44
|
+
* happens to be rendered. Give both or neither.
|
|
45
|
+
*/
|
|
46
|
+
offset?: number;
|
|
47
|
+
/** The whole list's length, counting the carried item when it lives here. See `offset`. */
|
|
48
|
+
total?: number;
|
|
40
49
|
className?: string;
|
|
41
50
|
children?: ReactNode;
|
|
42
51
|
}
|
|
43
|
-
export declare function DragDropContainer({ id, label, axis, className, children, }: DragDropContainerProps): import("react").JSX.Element;
|
|
52
|
+
export declare function DragDropContainer({ id, label, axis, offset, total, className, children, }: DragDropContainerProps): import("react").JSX.Element;
|
|
44
53
|
export interface DragDropItemProps {
|
|
45
54
|
id: DragId;
|
|
46
55
|
/**
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { type ReactNode } from "react";
|
|
2
|
+
import type { PagedWindow } from "../lib/paged-window";
|
|
3
|
+
export interface PagedWindowStrings {
|
|
4
|
+
/** Shown in a page whose request failed. */
|
|
5
|
+
error: string;
|
|
6
|
+
/** The retry button's label. */
|
|
7
|
+
retry: string;
|
|
8
|
+
}
|
|
9
|
+
export interface PagedWindowItemPosition {
|
|
10
|
+
/** Position among the items currently shown, across every shown page. */
|
|
11
|
+
local: number;
|
|
12
|
+
/** Position in the whole list: `source.offset + local`. */
|
|
13
|
+
global: number;
|
|
14
|
+
}
|
|
15
|
+
export interface PagedWindowListProps<T> {
|
|
16
|
+
/** The window to render — what `usePagedWindow` returned. */
|
|
17
|
+
source: PagedWindow<T>;
|
|
18
|
+
/** Stable across renders and pages. Never an array index. */
|
|
19
|
+
getKey: (item: T) => string | number;
|
|
20
|
+
renderItem: (item: T, position: PagedWindowItemPosition) => ReactNode;
|
|
21
|
+
/**
|
|
22
|
+
* One item's height INCLUDING its spacing, in px, used for placeholders until
|
|
23
|
+
* real pages have been measured. Replaced by the measured mean as pages
|
|
24
|
+
* arrive, so it only has to be close.
|
|
25
|
+
*/
|
|
26
|
+
estimatedItemHeight: number;
|
|
27
|
+
/** Words in the failed-page row. */
|
|
28
|
+
strings?: Partial<PagedWindowStrings>;
|
|
29
|
+
className?: string;
|
|
30
|
+
}
|
|
31
|
+
export declare function PagedWindowList<T>({ source, getKey, renderItem, estimatedItemHeight, strings, className, }: PagedWindowListProps<T>): import("react").JSX.Element;
|
|
@@ -21,6 +21,7 @@ export declare const board: import("tailwind-variants").TVReturnType<{
|
|
|
21
21
|
header: string[];
|
|
22
22
|
title: string;
|
|
23
23
|
glyph: string;
|
|
24
|
+
trailing: string;
|
|
24
25
|
label: string;
|
|
25
26
|
list: string;
|
|
26
27
|
empty: string;
|
|
@@ -50,6 +51,7 @@ export declare const board: import("tailwind-variants").TVReturnType<{
|
|
|
50
51
|
header: string[];
|
|
51
52
|
title: string;
|
|
52
53
|
glyph: string;
|
|
54
|
+
trailing: string;
|
|
53
55
|
label: string;
|
|
54
56
|
list: string;
|
|
55
57
|
empty: string;
|
|
@@ -79,6 +81,7 @@ export declare const board: import("tailwind-variants").TVReturnType<{
|
|
|
79
81
|
header: string[];
|
|
80
82
|
title: string;
|
|
81
83
|
glyph: string;
|
|
84
|
+
trailing: string;
|
|
82
85
|
label: string;
|
|
83
86
|
list: string;
|
|
84
87
|
empty: string;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { Ref } from "react";
|
|
2
|
+
export interface PlaceholderBlockProps {
|
|
3
|
+
page: number;
|
|
4
|
+
/** `unheld` is a page outside the window: empty space of the right height. */
|
|
5
|
+
status: "unheld" | "loading" | "error";
|
|
6
|
+
/** The height of the page this stands for: items on it x the measured item height. */
|
|
7
|
+
height: number;
|
|
8
|
+
/** One item's height, for the loading rows. */
|
|
9
|
+
pitch: number;
|
|
10
|
+
/** Ref callback that returns its cleanup: how the list's observers find this block. */
|
|
11
|
+
blockRef: Ref<HTMLDivElement>;
|
|
12
|
+
errorText: string;
|
|
13
|
+
retryText: string;
|
|
14
|
+
onRetry: () => void;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* The block of a page that has no cards to show: a page outside the window, one
|
|
18
|
+
* on its way, or one that failed. It holds the page's height either way, so the
|
|
19
|
+
* scrollbar does not move when the cards arrive.
|
|
20
|
+
*/
|
|
21
|
+
export declare function PlaceholderBlock({ page, status, height, pitch, blockRef, errorText, retryText, onRetry, }: PlaceholderBlockProps): import("react").JSX.Element;
|
package/dist/types/index.d.ts
CHANGED
|
@@ -111,10 +111,12 @@ export { tokenize, listLanguages, resolveLanguage, defineGrammar, registerGramma
|
|
|
111
111
|
export { parseMarkdown, MarkdownNodes, sanitizeHref, type MdNode, type MdInline, type MdCodeRenderer, } from "./lib/syntax/markdown";
|
|
112
112
|
export { DragDrop, DragDropContainer, DragDropItem, DragDropHandle, DragDropSurface, type DragDropProps, type DragDropContainerProps, type DragDropItemProps, type DragDropHandleProps, type DragDropSurfaceProps, } from "./components/DragDrop";
|
|
113
113
|
export { KanbanBoard, type KanbanBoardProps, type KanbanLane, type KanbanMove, type KanbanRefusal, } from "./components/kanban/KanbanBoard";
|
|
114
|
-
export { DEFAULT_DRAG_STRINGS, DRAG_INSTRUCTIONS_ID, DragInstructions, DragLiveRegion, DragSession, DragSessionProvider, applyMove, attachAutoScroll, attachKeyboardSensor, attachPointerSensor, attachSessionWindowListeners, isNoOp, resolveTarget, sameTarget, scrollVelocity, useCreateDragSession, useDragAnnouncements, useDragSession, useDragState, useDraggable, useDropContainer, type ContainerId, type DragAxis, type DragCancelEvent, type DragEndEvent, type DragId, type DragMode, type DragPhase, type DragSessionCallbacks, type DragStartEvent, type DragState, type AutoScrollOptions, type DragAnnouncementContext, type DragAnnouncementOptions, type DragAnnouncements, type DragInstructionsProps, type DragLiveRegionProps, type DragStrings, type DropTarget, type KeyboardSensorOptions, type PointerActivation, } from "./lib/dnd";
|
|
114
|
+
export { DEFAULT_DRAG_STRINGS, DRAG_INSTRUCTIONS_ID, DragInstructions, DragLiveRegion, DragSession, DragSessionProvider, applyMove, attachAutoScroll, attachKeyboardSensor, attachPointerSensor, attachSessionWindowListeners, isNoOp, resolveTarget, sameTarget, scrollVelocity, useCreateDragSession, useDragAnnouncements, useDragSession, useDragState, useDragLive, useDraggable, useDropContainer, type ContainerId, type DragAxis, type DragCancelEvent, type DragEndEvent, type DragId, type DragMode, type DragPhase, type DragSessionCallbacks, type DragStartEvent, type DragState, type AutoScrollOptions, type DragAnnouncementContext, type DragAnnouncementOptions, type DragAnnouncements, type DragInstructionsProps, type DragLiveRegionProps, type DragStrings, type DropTarget, type KeyboardSensorOptions, type PointerActivation, } from "./lib/dnd";
|
|
115
115
|
export { Board, BoardColumn, BoardCard, BoardCardSurface, type BoardProps, type BoardColumnProps, type BoardCardProps, type BoardCardSurfaceProps, } from "./components/Board";
|
|
116
|
-
export { buildBoardModel, resolveMove, applyBoardMoves, useBoard, type BoardModel, type UseBoardResult, } from "./lib/board";
|
|
117
|
-
export type { BoardAccessors, BoardColumnDef, BoardColumnId, BoardColumnModel, BoardItemId, BoardMove, BoardMoveRequest, BoardMoveSource, BoardMoveVerdict, UseBoardOptions, } from "./lib/board";
|
|
116
|
+
export { buildBoardModel, resolveMove, applyBoardMoves, useBoard, moveAnchor, type BoardModel, type MoveAnchor, type UseBoardResult, } from "./lib/board";
|
|
117
|
+
export type { BoardAccessors, BoardColumnDef, BoardEdge, BoardColumnId, BoardColumnModel, BoardItemId, BoardMove, BoardMoveRequest, BoardMoveSource, BoardMoveVerdict, UseBoardOptions, } from "./lib/board";
|
|
118
|
+
export { PagedWindowList, type PagedWindowItemPosition, type PagedWindowListProps, type PagedWindowStrings, } from "./components/PagedWindowList";
|
|
119
|
+
export { PagedWindowStore, usePagedWindow, windowPageNumbers, type LoadPage, type LoadPageContext, type PageResponse, type PageSnapshot, type PageStatus, type PagedWindow, type PagedWindowOptions, type PagedWindowSnapshot, type UsePagedWindowOptions, } from "./lib/paged-window";
|
|
118
120
|
export type { MessageRole, MessagePart, TextPart, CodePart, ReasoningPart, ToolPart, ToolStatus, FilePart, ComponentPart, ChatFile, ChatMessage, RenderContext, PartRenderer, PartRenderers, ChatComponentRenderer, ChatComponents, } from "./components/chat/types";
|
|
119
121
|
export { Chat, ChatPopover, type ChatProps, type ChatPopoverProps, type ChatSurface, } from "./components/chat/Chat";
|
|
120
122
|
export { Message, MessagePartView, ToolCall, type MessageProps, type MessageAction, type MessagePartViewProps, type ToolCallProps, } from "./components/chat/Message";
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ==== MOVE ANCHOR ====
|
|
3
|
+
*
|
|
4
|
+
* What a server needs to place a card the board moved, when the board holds
|
|
5
|
+
* only a window of the column (DX-4540). A drop knows exactly one thing about
|
|
6
|
+
* where it landed: which LOADED neighbours it is between. It never knows the
|
|
7
|
+
* card's position in the whole column, and a client that tries to compute one
|
|
8
|
+
* from `to.index` is wrong the moment the window does not start at the top.
|
|
9
|
+
* So the request names the neighbours, and the server finds the place.
|
|
10
|
+
*
|
|
11
|
+
* `to.index` counts the destination's LOADED cards with the moved card already
|
|
12
|
+
* removed (the board's one index rule), so the card lands before
|
|
13
|
+
* `rest[to.index]` and after `rest[to.index - 1]`, where `rest` is the
|
|
14
|
+
* destination's loaded cards without the moved one. Either neighbour may be
|
|
15
|
+
* absent: at the start of the loaded run there is nothing loaded above, at the
|
|
16
|
+
* end nothing below, and an empty destination has neither. A server that is
|
|
17
|
+
* given `beforeId` places the card directly above it; given only `afterId`,
|
|
18
|
+
* directly below it; given neither, the destination was empty.
|
|
19
|
+
*
|
|
20
|
+
* AN EDGE INTENT TRUMPS THE NEIGHBOURS. "Move to top" in a window that starts
|
|
21
|
+
* at card 221 means the top of the column, which no loaded neighbour can say,
|
|
22
|
+
* so the anchor carries the edge and no ids.
|
|
23
|
+
*/
|
|
24
|
+
import type { BoardEdge, BoardItemId, BoardMove } from "./types";
|
|
25
|
+
export interface MoveAnchor {
|
|
26
|
+
/** The loaded card the moved card lands directly above. */
|
|
27
|
+
beforeId?: BoardItemId;
|
|
28
|
+
/** The loaded card the moved card lands directly below. */
|
|
29
|
+
afterId?: BoardItemId;
|
|
30
|
+
/** The moved card goes to the very top or bottom of the whole column; no ids are given. */
|
|
31
|
+
edge?: BoardEdge;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* @param destinationItems the destination column's LOADED cards (the moved card
|
|
35
|
+
* may be among them, for a same-column move).
|
|
36
|
+
*/
|
|
37
|
+
export declare function moveAnchor<T>(destinationItems: readonly T[], move: BoardMove, getId: (item: T) => BoardItemId): MoveAnchor;
|
|
@@ -8,7 +8,8 @@
|
|
|
8
8
|
* NOT wired into the package's top-level `src/index.ts` — that barrel is
|
|
9
9
|
* owned by the contract's author, not this agent (see this round's brief).
|
|
10
10
|
*/
|
|
11
|
-
export type { BoardAccessors, BoardColumnDef, BoardColumnId, BoardColumnModel, BoardItemId, BoardMove, BoardMoveRequest, BoardMoveSource, BoardMoveVerdict, UseBoardOptions, } from "./types";
|
|
11
|
+
export type { BoardAccessors, BoardColumnDef, BoardEdge, BoardColumnId, BoardColumnModel, BoardItemId, BoardMove, BoardMoveRequest, BoardMoveSource, BoardMoveVerdict, UseBoardOptions, } from "./types";
|
|
12
12
|
export { buildBoardModel, type BoardModel } from "./model";
|
|
13
|
+
export { moveAnchor, type MoveAnchor } from "./anchor";
|
|
13
14
|
export { applyBoardMoves, resolveMove, type ResolveMoveContext, type ResolvedMove, } from "./move";
|
|
14
15
|
export { useBoard, type UseBoardResult } from "./use-board";
|
|
@@ -74,11 +74,42 @@ export interface BoardColumnDef {
|
|
|
74
74
|
* Soft cap on item count. Exceeding it is a VISUAL event, not a blocked move
|
|
75
75
|
* — a WIP limit exists to interrupt a human, and a board that refuses the
|
|
76
76
|
* drop instead just teaches people to work around the board. The core reports
|
|
77
|
-
* `overWip`; the view layer is responsible for making it loud.
|
|
77
|
+
* `overWip`; the view layer is responsible for making it loud. The limit is
|
|
78
|
+
* judged against the column's real length (`total`), not what is loaded.
|
|
78
79
|
*/
|
|
79
80
|
wipLimit?: number;
|
|
81
|
+
/**
|
|
82
|
+
* DX-4540: the column's real length when `items` holds only the cards loaded so
|
|
83
|
+
* far (a windowed lane). The model's `count` is this number, and `overWip`,
|
|
84
|
+
* the move menu's "is there anything above/below" and the drag announcements
|
|
85
|
+
* all read it. `null` means the length is still being read: the column is
|
|
86
|
+
* neither empty nor over its limit, and `count` falls back to the loaded
|
|
87
|
+
* cards. Omitted, every card of the column is loaded and the count is theirs.
|
|
88
|
+
*/
|
|
89
|
+
total?: number | null;
|
|
90
|
+
/**
|
|
91
|
+
* DX-4540: the position in the whole column of the first loaded card, so a
|
|
92
|
+
* card at index 0 of `items` is spoken as "position 221", not "position 1".
|
|
93
|
+
* Only meaningful with `total`; the loaded cards must be one contiguous run
|
|
94
|
+
* of the column. @default 0
|
|
95
|
+
*/
|
|
96
|
+
offset?: number;
|
|
80
97
|
}
|
|
81
|
-
/**
|
|
98
|
+
/**
|
|
99
|
+
* Where a move lands beyond what is loaded. In a windowed column `to.index` can
|
|
100
|
+
* only name a position among the LOADED cards, so a move to "the very top" or
|
|
101
|
+
* "the very bottom" of the column says so here and the consumer sends that
|
|
102
|
+
* intent to its server (see `moveAnchor`). In a column with every card loaded
|
|
103
|
+
* the edge is the same place as `index` 0 / the end, so a consumer that ignores
|
|
104
|
+
* it is still right.
|
|
105
|
+
*/
|
|
106
|
+
export type BoardEdge = "top" | "bottom";
|
|
107
|
+
/**
|
|
108
|
+
* One item's move. `to.index` counts the destination list without this item —
|
|
109
|
+
* the LOADED list, in a windowed column. `to.edge`, when present, is the
|
|
110
|
+
* intent the index cannot carry; the index is then the nearest loaded position
|
|
111
|
+
* (0 for `"top"`, one past the last loaded card for `"bottom"`).
|
|
112
|
+
*/
|
|
82
113
|
export interface BoardMove {
|
|
83
114
|
itemId: BoardItemId;
|
|
84
115
|
from: {
|
|
@@ -88,6 +119,7 @@ export interface BoardMove {
|
|
|
88
119
|
to: {
|
|
89
120
|
columnId: BoardColumnId;
|
|
90
121
|
index: number;
|
|
122
|
+
edge?: BoardEdge;
|
|
91
123
|
};
|
|
92
124
|
}
|
|
93
125
|
/**
|
|
@@ -125,11 +157,22 @@ export type BoardMoveVerdict = {
|
|
|
125
157
|
export interface BoardColumnModel<T> {
|
|
126
158
|
id: BoardColumnId;
|
|
127
159
|
label: string;
|
|
160
|
+
/** The cards loaded. All of the column's cards unless the column is windowed. */
|
|
128
161
|
items: readonly T[];
|
|
129
|
-
/**
|
|
162
|
+
/**
|
|
163
|
+
* The column's length: its `total` when the definition gave one, else the
|
|
164
|
+
* loaded cards. Count is on the model because the view needs it before it
|
|
165
|
+
* renders rows — and because it is computed ONCE here: the badge, the empty
|
|
166
|
+
* state, the WIP verdict, the move menu and the announcements all read this
|
|
167
|
+
* number, never their own.
|
|
168
|
+
*/
|
|
130
169
|
count: number;
|
|
170
|
+
/** `true` while a windowed column's length is still being read (`total: null`). `count` is then the loaded cards. */
|
|
171
|
+
counting: boolean;
|
|
172
|
+
/** Position in the whole column of `items[0]`; 0 unless the column is windowed. */
|
|
173
|
+
offset: number;
|
|
131
174
|
wipLimit: number | undefined;
|
|
132
|
-
/** `true` when `count` exceeds `wipLimit`. Always false with no limit set
|
|
175
|
+
/** `true` when `count` exceeds `wipLimit`. Always false with no limit set, and while `counting`. */
|
|
133
176
|
overWip: boolean;
|
|
134
177
|
}
|
|
135
178
|
export interface UseBoardOptions<T> {
|
|
@@ -7,7 +7,7 @@ export interface DragAnnouncementContext {
|
|
|
7
7
|
label: string;
|
|
8
8
|
container: ContainerId;
|
|
9
9
|
containerLabel: string;
|
|
10
|
-
/** 1-based, as spoken. */
|
|
10
|
+
/** 1-based, as spoken, in the WHOLE container — a windowed container reports its real offset and length (DX-4540). */
|
|
11
11
|
position: number;
|
|
12
12
|
/**
|
|
13
13
|
* The list length WITH the carried item counted in it, so `position` is never
|
|
@@ -12,5 +12,5 @@ export { DragSession, type StartArgs } from "./session";
|
|
|
12
12
|
export { attachPointerSensor, attachSessionWindowListeners, type PointerActivation, type PointerSensorOptions, } from "./sensors/pointer";
|
|
13
13
|
export { attachKeyboardSensor, type KeyboardSensorOptions, } from "./sensors/keyboard";
|
|
14
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";
|
|
15
|
+
export { DragSessionProvider, useCreateDragSession, useDragSession, useDragState, useDragLive, useDraggable, useDropContainer, type UseDraggableOptions, type UseDraggableResult, type UseDropContainerOptions, type UseDropContainerResult, } from "./use-drag";
|
|
16
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";
|
|
@@ -94,6 +94,18 @@ export declare class DragSession {
|
|
|
94
94
|
cancel(): void;
|
|
95
95
|
/** The container the pointer is over right now, or null. */
|
|
96
96
|
containerAtPoint(point: Point): string | null;
|
|
97
|
+
/**
|
|
98
|
+
* Where a drop target sits, as a person is told it: 1-based, in the WHOLE
|
|
99
|
+
* container, with the whole container's length counted with the carried item
|
|
100
|
+
* in it. A container that renders every item is its own whole (the items
|
|
101
|
+
* registered under it); a windowed one says so at registration (`offset`,
|
|
102
|
+
* `total`) and the answer follows. `index` counts the container with the
|
|
103
|
+
* carried item already removed — the engine's one index rule.
|
|
104
|
+
*/
|
|
105
|
+
spokenPosition(container: string, index: number, carried: DragId): {
|
|
106
|
+
position: number;
|
|
107
|
+
total: number;
|
|
108
|
+
};
|
|
97
109
|
/** How many items a container holds once the carried one is taken out. */
|
|
98
110
|
slotCount(container: string): number;
|
|
99
111
|
}
|
|
@@ -95,6 +95,15 @@ export interface ContainerRegistration {
|
|
|
95
95
|
id: ContainerId;
|
|
96
96
|
element: HTMLElement;
|
|
97
97
|
axis: DragAxis;
|
|
98
|
+
/**
|
|
99
|
+
* DX-4540: for a container that renders only a window of a longer list. The
|
|
100
|
+
* position in the whole list of its first registered item, so a drop spoken
|
|
101
|
+
* as "position 221 of 500" is true. Absent for a container that holds every
|
|
102
|
+
* item.
|
|
103
|
+
*/
|
|
104
|
+
offset?: number;
|
|
105
|
+
/** The whole list's length, the carried item counted in it when it lives here. See `offset`. */
|
|
106
|
+
total?: number;
|
|
98
107
|
}
|
|
99
108
|
/** One item's measured box, taken at snapshot time. */
|
|
100
109
|
export interface ItemRect {
|
|
@@ -14,6 +14,16 @@ export declare function useDragSession(component: string): DragSession;
|
|
|
14
14
|
export declare function useCreateDragSession(callbacks: DragSessionCallbacks): DragSession;
|
|
15
15
|
/** Subscribe to the session. */
|
|
16
16
|
export declare function useDragState(session: DragSession): DragState;
|
|
17
|
+
/**
|
|
18
|
+
* True while a card is being carried in the enclosing `<DragDrop>`, and false
|
|
19
|
+
* outside one — it never throws, because the callers that ask (a windowed list
|
|
20
|
+
* deciding whether it may drop a page) are ordinary components that are
|
|
21
|
+
* sometimes inside a board and sometimes not.
|
|
22
|
+
*
|
|
23
|
+
* Reads only `phase === "dragging"`, so it re-renders its caller when a drag
|
|
24
|
+
* starts or ends and not on every pointer move, which `useDragState` would.
|
|
25
|
+
*/
|
|
26
|
+
export declare function useDragLive(): boolean;
|
|
17
27
|
export interface UseDraggableOptions {
|
|
18
28
|
id: DragId;
|
|
19
29
|
container: ContainerId;
|
|
@@ -45,6 +55,10 @@ export declare function useDraggable(session: DragSession, options: UseDraggable
|
|
|
45
55
|
export interface UseDropContainerOptions {
|
|
46
56
|
id: ContainerId;
|
|
47
57
|
axis?: DragAxis;
|
|
58
|
+
/** See `ContainerRegistration.offset` — a windowed container's first item's place in the whole list. */
|
|
59
|
+
offset?: number;
|
|
60
|
+
/** See `ContainerRegistration.total` — a windowed container's whole length. */
|
|
61
|
+
total?: number;
|
|
48
62
|
}
|
|
49
63
|
export interface UseDropContainerResult {
|
|
50
64
|
setContainerRef: (el: HTMLElement | null) => void;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export type ScrollAxis = "x" | "y";
|
|
2
|
+
export interface ScrollBoxOptions {
|
|
3
|
+
/** The axes that count; a box scrolling along any of them qualifies. @default ["y"] */
|
|
4
|
+
axes?: readonly ScrollAxis[];
|
|
5
|
+
/** Also require the content to overflow the box on that axis, not merely allow scrolling. @default false */
|
|
6
|
+
mustOverflow?: boolean;
|
|
7
|
+
}
|
|
8
|
+
export interface ScrollAncestorOptions extends ScrollBoxOptions {
|
|
9
|
+
/** Whether `from` itself may be the answer. Required: it is a statement about the caller. */
|
|
10
|
+
includeSelf: boolean;
|
|
11
|
+
}
|
|
12
|
+
/** Does `el` scroll along any of the given axes? */
|
|
13
|
+
export declare function isScrollBox(el: HTMLElement, { axes, mustOverflow }?: ScrollBoxOptions): boolean;
|
|
14
|
+
/** The nearest box that scrolls, walking up from `from`; null when none does. */
|
|
15
|
+
export declare function nearestScrollAncestor(from: Element | null, { includeSelf, ...box }: ScrollAncestorOptions): HTMLElement | null;
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
export { PagedWindowStore, windowPageNumbers, type LoadCount, type LoadPage, type LoadPageContext, type PageResponse, type PageSnapshot, type PageStatus, type PagedWindowMode, type PagedWindowOptions, type PagedWindowSnapshot, } from "./store";
|
|
2
|
+
export { usePagedWindow, type PagedWindow, type UsePagedWindowOptions } from "./use-paged-window";
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
export interface PageResponse<T> {
|
|
2
|
+
/** This page's items, in order. Fewer than `pageSize` only for the last page. */
|
|
3
|
+
items: readonly T[];
|
|
4
|
+
/** The WHOLE list's length right now. Every response refreshes it. */
|
|
5
|
+
total: number;
|
|
6
|
+
}
|
|
7
|
+
export interface LoadPageContext {
|
|
8
|
+
/** Aborted when the page is dropped, re-requested or the window is reset. */
|
|
9
|
+
signal: AbortSignal;
|
|
10
|
+
}
|
|
11
|
+
export type LoadPage<T> = (page: number, context: LoadPageContext) => Promise<PageResponse<T>>;
|
|
12
|
+
/** Answers the list's length alone — no items. What a hidden (`count` mode) lane asks. */
|
|
13
|
+
export type LoadCount = (context: LoadPageContext) => Promise<number>;
|
|
14
|
+
export type PagedWindowMode = "pages" | "count";
|
|
15
|
+
export type PageStatus = "loading" | "ready" | "error";
|
|
16
|
+
export interface PageSnapshot<T> {
|
|
17
|
+
page: number;
|
|
18
|
+
status: PageStatus;
|
|
19
|
+
/** Empty unless `ready`. A page being re-read keeps its old items and stays `ready`. */
|
|
20
|
+
items: readonly T[];
|
|
21
|
+
/** True while a `ready` page is being re-read in place. */
|
|
22
|
+
refreshing: boolean;
|
|
23
|
+
/** What the load rejected with. Only on `error`. */
|
|
24
|
+
error: unknown;
|
|
25
|
+
/** Part of the loaded run — its items are in `items`. A ready page past a hole is not, until the hole closes. */
|
|
26
|
+
shown: boolean;
|
|
27
|
+
}
|
|
28
|
+
export interface PagedWindowSnapshot<T> {
|
|
29
|
+
mode: PagedWindowMode;
|
|
30
|
+
/** The list's length as the latest response reported it; null before the first. */
|
|
31
|
+
total: number | null;
|
|
32
|
+
pageSize: number;
|
|
33
|
+
/** 0 until `total` is known, then `ceil(total / pageSize)`. */
|
|
34
|
+
pageCount: number;
|
|
35
|
+
/** The page the viewer is looking at. */
|
|
36
|
+
anchorPage: number;
|
|
37
|
+
/** Counts resets, so a view can scroll back to the top when the query changed. */
|
|
38
|
+
epoch: number;
|
|
39
|
+
/** The held pages, ascending. At most `windowPages` of them. */
|
|
40
|
+
pages: readonly PageSnapshot<T>[];
|
|
41
|
+
/** The loaded run: the items of the shown pages, in list order. At most `windowPages * pageSize`. */
|
|
42
|
+
items: readonly T[];
|
|
43
|
+
/** The position in the whole list of `items[0]`; 0 when nothing is loaded. */
|
|
44
|
+
offset: number;
|
|
45
|
+
/** What the last `loadCount` rejected with; undefined otherwise. */
|
|
46
|
+
countError: unknown;
|
|
47
|
+
}
|
|
48
|
+
export interface PagedWindowOptions<T> {
|
|
49
|
+
pageSize: number;
|
|
50
|
+
/** How many pages are held at once. @default 3 */
|
|
51
|
+
windowPages?: number;
|
|
52
|
+
loadPage: LoadPage<T>;
|
|
53
|
+
/** Required to use `count` mode. */
|
|
54
|
+
loadCount?: LoadCount;
|
|
55
|
+
/** @default "pages" */
|
|
56
|
+
mode?: PagedWindowMode;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* The pages to hold for an anchor: `windowPages` of them centred on it (the
|
|
60
|
+
* extra page of an even window goes after), clipped to `[0, pageCount)`. Pure,
|
|
61
|
+
* so the rule is testable without a store.
|
|
62
|
+
*/
|
|
63
|
+
export declare function windowPageNumbers(anchorPage: number, pageCount: number, windowPages: number): number[];
|
|
64
|
+
export declare class PagedWindowStore<T> {
|
|
65
|
+
private readonly pageSize;
|
|
66
|
+
private readonly windowPages;
|
|
67
|
+
private loadPage;
|
|
68
|
+
private loadCount;
|
|
69
|
+
private held;
|
|
70
|
+
private total;
|
|
71
|
+
private anchor;
|
|
72
|
+
/** The anchor the held pages were last synced to; differs from `anchor` only while frozen. */
|
|
73
|
+
private appliedAnchor;
|
|
74
|
+
private mode;
|
|
75
|
+
private frozen;
|
|
76
|
+
private epoch;
|
|
77
|
+
private nextToken;
|
|
78
|
+
private countRequest;
|
|
79
|
+
private countError;
|
|
80
|
+
private snapshot;
|
|
81
|
+
private listeners;
|
|
82
|
+
constructor({ pageSize, windowPages, loadPage, loadCount, mode }: PagedWindowOptions<T>);
|
|
83
|
+
/**
|
|
84
|
+
* Replace the loaders. The React binding calls this after every commit so a
|
|
85
|
+
* consumer's loaders can close over fresh state without the store being
|
|
86
|
+
* rebuilt (rebuilding drops every held page). A change in WHAT is loaded is a
|
|
87
|
+
* `reset`, never this.
|
|
88
|
+
*/
|
|
89
|
+
setLoaders(loadPage: LoadPage<T>, loadCount: LoadCount | undefined): void;
|
|
90
|
+
getSnapshot: () => PagedWindowSnapshot<T>;
|
|
91
|
+
subscribe: (listener: () => void) => (() => void);
|
|
92
|
+
/** The viewer moved to another page. Ignored while frozen, applied on thaw. */
|
|
93
|
+
setAnchorPage(page: number): void;
|
|
94
|
+
/**
|
|
95
|
+
* While true the window neither loads nor drops pages, whatever the anchor or
|
|
96
|
+
* the total does. A drag needs it: the carried card is mounted in its page,
|
|
97
|
+
* and dropping that page mid-drag would unmount the card and cancel the drag.
|
|
98
|
+
*/
|
|
99
|
+
setFrozen(frozen: boolean): void;
|
|
100
|
+
/** `count` holds and requests no cards, only the length (see the file header). */
|
|
101
|
+
setMode(mode: PagedWindowMode): void;
|
|
102
|
+
/** The query changed: forget everything, return to the first page. */
|
|
103
|
+
reset(): void;
|
|
104
|
+
/** Re-read one held page in place. A page that is not held loads fresh when it is next wanted. */
|
|
105
|
+
invalidatePage(page: number): void;
|
|
106
|
+
/** Re-read every held page in place; in `count` mode, re-ask the length. */
|
|
107
|
+
invalidateAll(): void;
|
|
108
|
+
/** Load the first window (or the length). The binding calls it on mount, and again after a `stop`. */
|
|
109
|
+
start(): void;
|
|
110
|
+
/** Abort every request and release every page. The binding calls it on unmount. */
|
|
111
|
+
stop(): void;
|
|
112
|
+
private requireCountLoader;
|
|
113
|
+
private pageCount;
|
|
114
|
+
/**
|
|
115
|
+
* Pages to hold now. None in `count` mode. Page 0 alone before the first
|
|
116
|
+
* response (the count is unknown) and for an empty list: an empty page is
|
|
117
|
+
* still held, because it is what `invalidateAll` re-reads when the list gets
|
|
118
|
+
* its first item — an empty lane that holds nothing could never find out it
|
|
119
|
+
* was no longer empty.
|
|
120
|
+
*/
|
|
121
|
+
private wanted;
|
|
122
|
+
/** Bring the held pages to the wanted ones — unless frozen, when that waits for the thaw. */
|
|
123
|
+
private sync;
|
|
124
|
+
/** Start (or restart) a page's request; whatever was in flight is abandoned. */
|
|
125
|
+
private request;
|
|
126
|
+
private settle;
|
|
127
|
+
private requestCount;
|
|
128
|
+
private settleCount;
|
|
129
|
+
private abortCount;
|
|
130
|
+
private dropAll;
|
|
131
|
+
/**
|
|
132
|
+
* The longest unbroken stretch of ready pages inside the list, lowest on a
|
|
133
|
+
* tie. Pages past the list's end (it shrank while the window was frozen) are
|
|
134
|
+
* not part of any run.
|
|
135
|
+
*/
|
|
136
|
+
private loadedRun;
|
|
137
|
+
private build;
|
|
138
|
+
private publish;
|
|
139
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* How tall one item really is — measured, not guessed. DX-4540.
|
|
3
|
+
*
|
|
4
|
+
* A page that is not held is drawn as a placeholder of `items x pitch`, so the
|
|
5
|
+
* scrollbar is only as true as the pitch. `estimated` stands in until pages
|
|
6
|
+
* have been seen; after that the pitch is the MEAN over every item measured,
|
|
7
|
+
* which is a ResizeObserver over the page blocks.
|
|
8
|
+
*
|
|
9
|
+
* EVERY block is observed, because a block turns from placeholder to cards (and
|
|
10
|
+
* back) without its element being replaced: only a block that currently holds
|
|
11
|
+
* cards (`data-count`) is a measurement of what a card costs; a placeholder's
|
|
12
|
+
* own size is the estimate drawn back at us. A block that stops holding cards,
|
|
13
|
+
* or is removed, leaves its LAST reading in the mean (`departed`), so dropping
|
|
14
|
+
* a page does not make the estimate lurch and every placeholder above the
|
|
15
|
+
* viewer resize with it.
|
|
16
|
+
*
|
|
17
|
+
* A change under half a pixel is not published: re-laying-out every placeholder
|
|
18
|
+
* in a long list for sub-pixel drift is cost with no picture.
|
|
19
|
+
*/
|
|
20
|
+
export declare function useMeasuredPitch(estimated: number): {
|
|
21
|
+
pitch: number;
|
|
22
|
+
observe: (el: HTMLElement) => () => void;
|
|
23
|
+
};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { ScrollRoot } from "./use-scroll-root";
|
|
2
|
+
/**
|
|
3
|
+
* The page the window should centre on: the middle one (lower middle when
|
|
4
|
+
* even) of the pages whose blocks intersect the root. A viewport shorter than
|
|
5
|
+
* a page sees one or two; the window of three covers either. Null when nothing
|
|
6
|
+
* is in view (a hidden scroller), so the window keeps where it was.
|
|
7
|
+
*/
|
|
8
|
+
export declare function centrePage(visible: ReadonlySet<Element>): number | null;
|
|
9
|
+
/**
|
|
10
|
+
* Which pages are in view, from one IntersectionObserver over every page block,
|
|
11
|
+
* rooted at the scroller — no scroll listener, no polling. DX-4540.
|
|
12
|
+
*
|
|
13
|
+
* `observe(el)` is what a block's ref calls; it returns the cleanup. It is
|
|
14
|
+
* stable, and blocks that mount before the observer exists (it needs the
|
|
15
|
+
* resolved scroller) are picked up when it does. Whenever the set in view
|
|
16
|
+
* changes, `onCentre` is told the page to centre on.
|
|
17
|
+
*/
|
|
18
|
+
export declare function usePageVisibility(scroller: ScrollRoot | null, onCentre: (page: number) => void): {
|
|
19
|
+
observe: (el: HTMLElement) => () => void;
|
|
20
|
+
};
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { type PagedWindowMode, type PagedWindowOptions, type PagedWindowSnapshot } from "./store";
|
|
2
|
+
export interface UsePagedWindowOptions<T> extends PagedWindowOptions<T> {
|
|
3
|
+
/**
|
|
4
|
+
* Names WHAT is being listed: the filter, the sort, the time range. When it
|
|
5
|
+
* changes the window forgets every page, returns to the first and the list
|
|
6
|
+
* scrolls back to the top. A change in a loader's identity alone does not:
|
|
7
|
+
* a loader closing over fresh state must not throw away what is held.
|
|
8
|
+
*/
|
|
9
|
+
queryKey: string;
|
|
10
|
+
/**
|
|
11
|
+
* `"count"` loads no cards, only the list's length (through `loadCount`), and
|
|
12
|
+
* keeps it live: a hidden lane that still needs a true badge. Back to
|
|
13
|
+
* `"pages"`, the window loads again.
|
|
14
|
+
* @default "pages"
|
|
15
|
+
*/
|
|
16
|
+
mode?: PagedWindowMode;
|
|
17
|
+
}
|
|
18
|
+
export interface PagedWindow<T> extends PagedWindowSnapshot<T> {
|
|
19
|
+
/** The viewer moved to this page. `PagedWindowList` reports it; a custom view may too. */
|
|
20
|
+
setAnchorPage: (page: number) => void;
|
|
21
|
+
/** Re-read one held page in place — a card on it changed. */
|
|
22
|
+
invalidatePage: (page: number) => void;
|
|
23
|
+
/** Re-read every held page in place — a card was created, moved or removed. In `count` mode, re-ask the length. */
|
|
24
|
+
invalidateAll: () => void;
|
|
25
|
+
/**
|
|
26
|
+
* While true the window neither loads nor drops pages. `PagedWindowList`
|
|
27
|
+
* sets it for the length of a drag: the carried card is mounted in its page,
|
|
28
|
+
* and dropping that page mid-drag would unmount the card and cancel the drag.
|
|
29
|
+
*/
|
|
30
|
+
setFrozen: (frozen: boolean) => void;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Holds `windowPages` (default 3) pages of a long server-side list and tells a
|
|
34
|
+
* view which ones to show. `loadPage` answers one page AND the list's current
|
|
35
|
+
* length, so the scrollbar can span `total` before most of it is loaded.
|
|
36
|
+
*
|
|
37
|
+
* `pageSize` and `windowPages` are fixed for the life of the hook: changing one
|
|
38
|
+
* means every held page is cut differently, which is a different list — give it
|
|
39
|
+
* a new `queryKey` and a new mount instead. It throws rather than guessing.
|
|
40
|
+
*/
|
|
41
|
+
export declare function usePagedWindow<T>({ pageSize, windowPages, loadPage, loadCount, queryKey, mode, }: UsePagedWindowOptions<T>): PagedWindow<T>;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/** The element a list scrolls in: `el` is null when the page itself scrolls (the viewport). */
|
|
2
|
+
export interface ScrollRoot {
|
|
3
|
+
el: HTMLElement | null;
|
|
4
|
+
}
|
|
5
|
+
/**
|
|
6
|
+
* Finds the box a list scrolls in, once the list is in the DOM. DX-4540.
|
|
7
|
+
*
|
|
8
|
+
* Returns a ref callback for the list's root and the root it resolved (null
|
|
9
|
+
* until mounted). The list's OWN element counts: a list whose class sets
|
|
10
|
+
* `overflow-y-auto` is its own scroller, and one dropped into a board lane
|
|
11
|
+
* finds the lane's container above it. Resolved on mount only — a list does
|
|
12
|
+
* not change scroller during its life, and re-walking on every render would
|
|
13
|
+
* make the IntersectionObserver rooted at it be torn down and rebuilt.
|
|
14
|
+
*/
|
|
15
|
+
export declare function useScrollRoot(): {
|
|
16
|
+
ref: (el: HTMLElement | null) => void;
|
|
17
|
+
scroller: ScrollRoot | null;
|
|
18
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@danxbot/ui",
|
|
3
|
-
"version": "20.
|
|
3
|
+
"version": "20.11.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Danxbot — a domain-agnostic React design system with motion as a first-class primitive.",
|
|
6
6
|
"license": "MIT",
|
|
@@ -74,7 +74,7 @@
|
|
|
74
74
|
"gates": "npm run shoot && npm run a11y && node scripts/shoot-overlays.mjs && npm run probe",
|
|
75
75
|
"a11y": "node scripts/a11y-scan.mjs",
|
|
76
76
|
"shoot": "node scripts/shoot.mjs",
|
|
77
|
-
"probe": "node scripts/probe-scale.mjs && node scripts/probe-switch.mjs && node scripts/probe-slider.mjs && node scripts/probe-panel.mjs && node scripts/probe-popover-width.mjs && node scripts/probe-api.mjs && node scripts/probe-api-live.mjs && node scripts/probe-tabs-overflow.mjs && node scripts/probe-stat-row.mjs && node scripts/probe-packaging.mjs && node scripts/probe-containment.mjs && node scripts/probe-popover-containment.mjs && node scripts/probe-popover-panel-width.mjs && node scripts/probe-theme-menu-overflow.mjs && node scripts/probe-shell.mjs && node scripts/probe-nav-trigger-tap-target.mjs && node scripts/probe-attachment-touch.mjs && node scripts/probe-dense-button.mjs && node scripts/probe-card-header.mjs && node scripts/probe-inline-edit.mjs && node scripts/probe-read-more.mjs && node scripts/probe-section-nav.mjs && node scripts/probe-attention.mjs",
|
|
77
|
+
"probe": "node scripts/probe-scale.mjs && node scripts/probe-switch.mjs && node scripts/probe-slider.mjs && node scripts/probe-panel.mjs && node scripts/probe-popover-width.mjs && node scripts/probe-api.mjs && node scripts/probe-api-live.mjs && node scripts/probe-tabs-overflow.mjs && node scripts/probe-stat-row.mjs && node scripts/probe-packaging.mjs && node scripts/probe-containment.mjs && node scripts/probe-popover-containment.mjs && node scripts/probe-popover-panel-width.mjs && node scripts/probe-theme-menu-overflow.mjs && node scripts/probe-shell.mjs && node scripts/probe-nav-trigger-tap-target.mjs && node scripts/probe-attachment-touch.mjs && node scripts/probe-dense-button.mjs && node scripts/probe-card-header.mjs && node scripts/probe-inline-edit.mjs && node scripts/probe-read-more.mjs && node scripts/probe-section-nav.mjs && node scripts/probe-attention.mjs && node scripts/probe-paged-window.mjs",
|
|
78
78
|
"sync": "node scripts/sync.mjs",
|
|
79
79
|
"visual": "node scripts/visual-docker.mjs",
|
|
80
80
|
"visual:update": "node scripts/visual-docker.mjs --update-snapshots",
|