@danxbot/ui 11.1.0 → 12.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.
- package/dist/index.js +11116 -10913
- package/dist/index.js.map +1 -1
- package/dist/types/components/EditGutter.d.ts +17 -7
- package/dist/types/components/InlineEdit.d.ts +44 -31
- package/dist/types/components/InlineMarkdownEdit.d.ts +25 -7
- package/dist/types/components/MarkdownEditor.d.ts +7 -0
- package/dist/types/components/edit/activation.d.ts +21 -0
- package/dist/types/components/edit/outcome.d.ts +17 -6
- package/dist/types/components/edit/use-edit-session.d.ts +55 -0
- package/dist/types/index.d.ts +4 -2
- package/package.json +1 -1
|
@@ -1,10 +1,6 @@
|
|
|
1
1
|
import { type ReactNode } from "react";
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
editing: boolean;
|
|
5
|
-
onEditingChange: (editing: boolean) => void;
|
|
6
|
-
}
|
|
7
|
-
export interface EditGutterProps {
|
|
2
|
+
import { type EditActivation } from "./edit/activation";
|
|
3
|
+
interface EditGutterFieldProps {
|
|
8
4
|
/**
|
|
9
5
|
* The pencil's accessible name — say WHAT it edits ("Edit title"), because
|
|
10
6
|
* a screen reader lists it among every other pencil on the page.
|
|
@@ -15,6 +11,20 @@ export interface EditGutterProps {
|
|
|
15
11
|
* `{(edit) => <InlineEdit {...edit} … />}`.
|
|
16
12
|
*/
|
|
17
13
|
children: (activation: EditActivation) => ReactNode;
|
|
14
|
+
/** Render no pencil — the field cannot be edited here. */
|
|
15
|
+
disabled?: boolean;
|
|
18
16
|
className?: string;
|
|
19
17
|
}
|
|
20
|
-
|
|
18
|
+
/**
|
|
19
|
+
* The gutter keeps `editing` itself (omit both), or the caller owns it: pass
|
|
20
|
+
* `editing` AND `onEditingChange` (`EditActivation`) — the pencil then asks
|
|
21
|
+
* through `onEditingChange(true)` and the field's close arrives through
|
|
22
|
+
* `onEditingChange(false)`. One without the other is a type error and throws
|
|
23
|
+
* at render, for the reason `assertActivationPair` gives.
|
|
24
|
+
*/
|
|
25
|
+
export type EditGutterProps = EditGutterFieldProps & ({
|
|
26
|
+
editing?: undefined;
|
|
27
|
+
onEditingChange?: (editing: boolean) => void;
|
|
28
|
+
} | EditActivation);
|
|
29
|
+
export declare function EditGutter({ label, children, editing: editingProp, onEditingChange, disabled, className }: EditGutterProps): import("react").JSX.Element;
|
|
30
|
+
export {};
|
|
@@ -1,16 +1,19 @@
|
|
|
1
1
|
import { type Ref } from "react";
|
|
2
2
|
import { useRender } from "@base-ui/react/use-render";
|
|
3
|
-
import { type
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
import { type EditActivation } from "./edit/activation";
|
|
4
|
+
import { type EditCommit } from "./edit/outcome";
|
|
5
|
+
interface InlineEditFieldProps {
|
|
6
6
|
/**
|
|
7
7
|
* The committed value. Controlled — this component never holds the truth,
|
|
8
8
|
* only the draft while somebody is typing one.
|
|
9
9
|
*/
|
|
10
10
|
value: string;
|
|
11
11
|
/**
|
|
12
|
-
* Commit a new value. Called ONLY when the live text
|
|
13
|
-
* `value
|
|
12
|
+
* Commit a new value. Called ONLY when the live text differs both from
|
|
13
|
+
* `value` and from the value editing began from — text the reader did not
|
|
14
|
+
* change is never written back, even if `value` moved underneath the open
|
|
15
|
+
* field (with `autosaveMs`, "unchanged" is measured against the last value
|
|
16
|
+
* this edit committed instead — see "AUTOSAVE").
|
|
14
17
|
*
|
|
15
18
|
* `base` is the `value` this control saw at the moment editing BEGAN —
|
|
16
19
|
* captured once and handed back unchanged regardless of how long the edit or
|
|
@@ -23,7 +26,7 @@ export interface InlineEditProps {
|
|
|
23
26
|
* it settles. Resolve/return `{ refusal }` to reject the value with a
|
|
24
27
|
* reason; throw or reject to report that the save itself failed.
|
|
25
28
|
*/
|
|
26
|
-
onCommit:
|
|
29
|
+
onCommit: EditCommit;
|
|
27
30
|
/**
|
|
28
31
|
* The refusals that are knowable WITHOUT a request. Return the reason, or
|
|
29
32
|
* `null` to accept. Runs after the unchanged check and before `onCommit`.
|
|
@@ -93,7 +96,10 @@ export interface InlineEditProps {
|
|
|
93
96
|
/**
|
|
94
97
|
* This value cannot be edited at all, AND WHY. `{ reason }`, matching
|
|
95
98
|
* `SettingRow.readOnly` — a control that refuses in silence is
|
|
96
|
-
* indistinguishable from one that is broken.
|
|
99
|
+
* indistinguishable from one that is broken. Under controlled activation a
|
|
100
|
+
* request to open (`editing` true) is declined by handing the closed state
|
|
101
|
+
* straight back through `onEditingChange(false)`, so a pencil driving it
|
|
102
|
+
* returns instead of waiting on a field that will never open (DX-3372 H3).
|
|
97
103
|
*/
|
|
98
104
|
readOnly?: {
|
|
99
105
|
reason: string;
|
|
@@ -102,33 +108,13 @@ export interface InlineEditProps {
|
|
|
102
108
|
* What is shown when the commit REJECTS (a thrown error or a rejected
|
|
103
109
|
* promise — never a `{ refusal }`, which renders its own text).
|
|
104
110
|
*
|
|
105
|
-
* @default "Could not save. Your text is still here — press
|
|
111
|
+
* @default "Could not save. Your text is still here — press <key> to try again.",
|
|
112
|
+
* where <key> is the key that commits in this configuration: Enter, or
|
|
113
|
+
* Ctrl+Enter for a `multiline` field without `autosaveMs`.
|
|
106
114
|
*/
|
|
107
115
|
failureMessage?: string;
|
|
108
116
|
/** The rejected error, handed on so it can be logged or reported. */
|
|
109
117
|
onError?: (error: unknown) => void;
|
|
110
|
-
/**
|
|
111
|
-
* CONTROLLED ACTIVATION (DX-3372). Pass it and the control never opens
|
|
112
|
-
* ITSELF — no click, Enter or F2 — because something else (the pencil of an
|
|
113
|
-
* `EditGutter`, a menu item, a shortcut) is the way in. Setting it `true`
|
|
114
|
-
* opens the field, focused, caret at the end; the control asks to close
|
|
115
|
-
* through `onEditingChange(false)` once a commit lands or Escape cancels.
|
|
116
|
-
* Setting it `false` yourself while it is open abandons the draft exactly as
|
|
117
|
-
* Escape would — nothing is written. At rest the element leaves the tab
|
|
118
|
-
* order and drops its hover affordance, so the trigger is the one stop.
|
|
119
|
-
*
|
|
120
|
-
* Omit it for the self-opening control (click, Enter or F2).
|
|
121
|
-
*/
|
|
122
|
-
editing?: boolean;
|
|
123
|
-
/**
|
|
124
|
-
* Fires exactly when the control itself starts or ends editing — never while
|
|
125
|
-
* a read-only control merely declines to open, and never for a change the
|
|
126
|
-
* caller made through `editing`. Use it to suspend any re-render-from-server
|
|
127
|
-
* logic of your own for as long as someone is actually looking at the
|
|
128
|
-
* field; see the module comment. With `editing` controlled, this is how the
|
|
129
|
-
* control hands the closed state back.
|
|
130
|
-
*/
|
|
131
|
-
onEditingChange?: (editing: boolean) => void;
|
|
132
118
|
/** Render as a different element — see `Button`'s own `render` for the shape. */
|
|
133
119
|
render?: useRender.RenderProp;
|
|
134
120
|
/**
|
|
@@ -143,4 +129,31 @@ export interface InlineEditProps {
|
|
|
143
129
|
messageClassName?: string;
|
|
144
130
|
ref?: Ref<HTMLElement>;
|
|
145
131
|
}
|
|
146
|
-
|
|
132
|
+
/**
|
|
133
|
+
* SELF-ACTIVATED: omit `editing` and the control opens itself on click, Enter
|
|
134
|
+
* or F2. `onEditingChange` then only observes — it fires when the control
|
|
135
|
+
* starts editing and when it stops, so a caller can suspend its own
|
|
136
|
+
* re-render-from-server logic for exactly as long as someone is typing.
|
|
137
|
+
*/
|
|
138
|
+
interface InlineEditSelfActivated {
|
|
139
|
+
editing?: undefined;
|
|
140
|
+
onEditingChange?: (editing: boolean) => void;
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* The props. `editing` + `onEditingChange` are a discriminated pair
|
|
144
|
+
* (DX-3372 review P0): either omit `editing` (self-activated, above) or pass
|
|
145
|
+
* BOTH (`EditActivation` — controlled). Controlled, the control never opens
|
|
146
|
+
* itself; `true` opens it focused with the caret at the end; it hands the
|
|
147
|
+
* closed state back through `onEditingChange(false)` after a landed commit,
|
|
148
|
+
* Escape, or a refused opening (read-only). Setting `editing` false yourself
|
|
149
|
+
* ends the edit exactly as Escape does — nothing more is written. A save that
|
|
150
|
+
* is already out when you do cannot be recalled: it still reaches your
|
|
151
|
+
* `onCommit` promise, but its outcome no longer touches the field — no
|
|
152
|
+
* message, no focus move, no second `onEditingChange(false)` — and a commit
|
|
153
|
+
* from the next opening waits for it before starting (see
|
|
154
|
+
* `edit/use-edit-session.ts`). At rest a controlled control leaves the tab
|
|
155
|
+
* order and drops its hover affordance, so the trigger is the one stop.
|
|
156
|
+
*/
|
|
157
|
+
export type InlineEditProps = InlineEditFieldProps & (InlineEditSelfActivated | EditActivation);
|
|
158
|
+
export declare function InlineEdit(props: InlineEditProps): import("react").JSX.Element;
|
|
159
|
+
export {};
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { type ReactNode } from "react";
|
|
2
2
|
import type { ImageUploader } from "../lib/markdown/image-pipeline";
|
|
3
|
-
import { type
|
|
4
|
-
|
|
3
|
+
import { type EditActivation } from "./edit/activation";
|
|
4
|
+
import { type EditCommit } from "./edit/outcome";
|
|
5
|
+
export interface InlineMarkdownEditProps extends EditActivation {
|
|
5
6
|
/** The committed markdown. Controlled — the field only ever holds a draft. */
|
|
6
7
|
value: string;
|
|
7
8
|
/**
|
|
@@ -10,11 +11,18 @@ export interface InlineMarkdownEditProps {
|
|
|
10
11
|
* began from; return/resolve `{ refusal }` to refuse with a reason, throw or
|
|
11
12
|
* reject to report a failed save.
|
|
12
13
|
*/
|
|
13
|
-
onCommit:
|
|
14
|
-
/**
|
|
14
|
+
onCommit: EditCommit;
|
|
15
|
+
/**
|
|
16
|
+
* Whether the editor is open — REQUIRED, see the module comment. The field
|
|
17
|
+
* hands the closed state back through `onEditingChange(false)` once a commit
|
|
18
|
+
* lands, on Escape, and when asked to open while `readOnly`. Setting it
|
|
19
|
+
* `false` yourself drops the draft exactly as Escape does. A save already
|
|
20
|
+
* out when you do cannot be recalled: your `onCommit` promise still settles,
|
|
21
|
+
* but its outcome no longer touches the field — no message, no focus move,
|
|
22
|
+
* no `onEditingChange(false)` — and a commit from the next opening waits for
|
|
23
|
+
* it before starting.
|
|
24
|
+
*/
|
|
15
25
|
editing: boolean;
|
|
16
|
-
/** The field asks to close through this, once a commit lands or Escape cancels. */
|
|
17
|
-
onEditingChange: (editing: boolean) => void;
|
|
18
26
|
/** The accessible name of the field and of its editor. */
|
|
19
27
|
label: string;
|
|
20
28
|
/** Shown at rest and in the editor when the value is empty. @default "Empty" */
|
|
@@ -25,13 +33,23 @@ export interface InlineMarkdownEditProps {
|
|
|
25
33
|
toolbar?: ReactNode;
|
|
26
34
|
/** Stores a pasted/dropped/picked image — see `MarkdownEditorProps.uploadImage`. */
|
|
27
35
|
uploadImage?: ImageUploader;
|
|
36
|
+
/**
|
|
37
|
+
* This value cannot be edited at all, AND WHY — `InlineEdit.readOnly`'s
|
|
38
|
+
* shape and rule: asked to open, the field hands `onEditingChange(false)`
|
|
39
|
+
* straight back, and the reason is the rest view's accessible description.
|
|
40
|
+
*/
|
|
41
|
+
readOnly?: {
|
|
42
|
+
reason: string;
|
|
43
|
+
};
|
|
28
44
|
/**
|
|
29
45
|
* What is shown when the commit REJECTS (never a `{ refusal }`, which
|
|
30
46
|
* renders its own text).
|
|
47
|
+
*
|
|
48
|
+
* @default "Could not save. Your text is still here — press Ctrl+Enter to try again."
|
|
31
49
|
*/
|
|
32
50
|
failureMessage?: string;
|
|
33
51
|
/** The rejected error, handed on so it can be logged or reported. */
|
|
34
52
|
onError?: (error: unknown) => void;
|
|
35
53
|
className?: string;
|
|
36
54
|
}
|
|
37
|
-
export declare function InlineMarkdownEdit(
|
|
55
|
+
export declare function InlineMarkdownEdit(props: InlineMarkdownEditProps): import("react").JSX.Element;
|
|
@@ -15,6 +15,13 @@ export interface MarkdownEditorHandle {
|
|
|
15
15
|
* `"end"` (snapped to a real caret stop); omit it to leave the caret wherever
|
|
16
16
|
* the browser puts it. DX-3372: `InlineMarkdownEdit` opens with the caret at
|
|
17
17
|
* the end, the same place `InlineEdit` puts it.
|
|
18
|
+
*
|
|
19
|
+
* THROWS rather than returning quietly (DX-3372 review P0) when it cannot do
|
|
20
|
+
* what it was asked: the surface is not built yet (it is created in an
|
|
21
|
+
* effect, so a render or a layout effect is too early), the editor is
|
|
22
|
+
* read-only, focus did not land on the surface, or the caret could not be
|
|
23
|
+
* placed. A caller that goes on to assume focus is inside would otherwise
|
|
24
|
+
* wait for a blur that never comes.
|
|
18
25
|
*/
|
|
19
26
|
focus: (caret?: "start" | "end") => void;
|
|
20
27
|
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The controlled activation pair every pencil-activated field takes, and the
|
|
3
|
+
* pair `EditGutter` hands out — so wiring a pencil to a field is one spread.
|
|
4
|
+
*
|
|
5
|
+
* `editing` says whether the field is open. The field calls
|
|
6
|
+
* `onEditingChange(false)` when it closes itself (a landed commit, Escape) and
|
|
7
|
+
* when it declines to open (read-only); it never calls `onEditingChange(true)`
|
|
8
|
+
* for an opening the caller made. The pair is all-or-nothing: a controlled
|
|
9
|
+
* `editing` with no way to hand the closed state back would leave the field
|
|
10
|
+
* shut after its first commit while the caller still believes it is open.
|
|
11
|
+
*/
|
|
12
|
+
export interface EditActivation {
|
|
13
|
+
editing: boolean;
|
|
14
|
+
onEditingChange: (editing: boolean) => void;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Throws when a caller controls `editing` without `onEditingChange`. The
|
|
18
|
+
* types already forbid it; this covers a caller that got past them (a cast,
|
|
19
|
+
* a spread of `any`, plain JavaScript). DX-3372 review P0.
|
|
20
|
+
*/
|
|
21
|
+
export declare function assertActivationPair(component: string, editing: boolean | undefined, onEditingChange: ((editing: boolean) => void) | undefined): void;
|
|
@@ -1,16 +1,27 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* The caller's refusal of a value, with the reason that will be shown.
|
|
3
3
|
*/
|
|
4
|
-
export interface
|
|
4
|
+
export interface EditRefusal {
|
|
5
5
|
refusal: string;
|
|
6
6
|
}
|
|
7
7
|
/** What `onCommit` may report. Returning nothing accepts the value. */
|
|
8
|
-
export type
|
|
8
|
+
export type EditOutcome = void | EditRefusal;
|
|
9
9
|
/** Every in-place editor's `onCommit`: the new value and the value editing began from. */
|
|
10
|
-
export type
|
|
11
|
-
export declare function isRefusal(outcome:
|
|
12
|
-
export declare function isPromise(outcome:
|
|
13
|
-
|
|
10
|
+
export type EditCommit = (value: string, base: string) => EditOutcome | Promise<EditOutcome>;
|
|
11
|
+
export declare function isRefusal(outcome: EditOutcome): outcome is EditRefusal;
|
|
12
|
+
export declare function isPromise(outcome: EditOutcome | Promise<EditOutcome>): outcome is Promise<EditOutcome>;
|
|
13
|
+
/**
|
|
14
|
+
* The key that retries a failed save in a given field: Enter where Enter
|
|
15
|
+
* commits, Ctrl+Enter where Enter inserts a newline. (Both fields accept
|
|
16
|
+
* Cmd+Enter as well as Ctrl+Enter, so "Ctrl+Enter" is true on every platform.)
|
|
17
|
+
*/
|
|
18
|
+
export type RetryKey = "Enter" | "Ctrl+Enter";
|
|
19
|
+
/**
|
|
20
|
+
* The default failure message. It names the retry key because blur never
|
|
21
|
+
* re-attempts a failed value — DX-3372 review L1: the single shared sentence
|
|
22
|
+
* said "press Enter" in fields where Enter inserts a newline.
|
|
23
|
+
*/
|
|
24
|
+
export declare function defaultFailureMessage(retryKey: RetryKey): string;
|
|
14
25
|
/**
|
|
15
26
|
* A refusal nobody can read leaves the field open with nothing beside it — the
|
|
16
27
|
* exact silent failure the outcome contract exists to rule out — so an empty
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { type EditCommit, type EditOutcome } from "./outcome";
|
|
2
|
+
export type SaveResult = {
|
|
3
|
+
outcome: EditOutcome;
|
|
4
|
+
} | {
|
|
5
|
+
error: unknown;
|
|
6
|
+
};
|
|
7
|
+
export interface EditSessionOptions {
|
|
8
|
+
/** Named in the thrown message when a refusal carries an empty reason. */
|
|
9
|
+
component: string;
|
|
10
|
+
/** The committed value — "unchanged" is measured against it. Read live. */
|
|
11
|
+
value: string;
|
|
12
|
+
onCommit: EditCommit;
|
|
13
|
+
onError?: (error: unknown) => void;
|
|
14
|
+
failureMessage: string;
|
|
15
|
+
/** End editing after a commit that landed (or had nothing to write). */
|
|
16
|
+
close: () => void;
|
|
17
|
+
/** Put focus back on the field after a refusal or a failure. */
|
|
18
|
+
refocus: () => void;
|
|
19
|
+
}
|
|
20
|
+
export interface EditSession {
|
|
21
|
+
pending: boolean;
|
|
22
|
+
refusal: string | null;
|
|
23
|
+
failure: string | null;
|
|
24
|
+
/** The message on screen, if any — a refusal or a failure. */
|
|
25
|
+
blocked: string | null;
|
|
26
|
+
/** The value this session began from — `onCommit`'s `base`. */
|
|
27
|
+
readonly base: string;
|
|
28
|
+
/** Start a new session. `refusal` seeds the message slot (live validation). */
|
|
29
|
+
begin: (base: string, refusal?: string | null) => void;
|
|
30
|
+
/** Finish the current session: every save still out becomes stale. */
|
|
31
|
+
end: () => void;
|
|
32
|
+
/** Commit `next` — unchanged check (see the module comment), `validate`, `onCommit`, outcome. */
|
|
33
|
+
commit: (next: string, validate?: (next: string) => string | null) => void;
|
|
34
|
+
refuse: (reason: string, source: "validate" | "onCommit") => void;
|
|
35
|
+
fail: (error: unknown) => void;
|
|
36
|
+
setPending: (pending: boolean) => void;
|
|
37
|
+
isPending: () => boolean;
|
|
38
|
+
setRefusal: (refusal: string | null) => void;
|
|
39
|
+
clearFailure: () => void;
|
|
40
|
+
/** The current session's token, for a caller that tracks its own save. */
|
|
41
|
+
token: () => number;
|
|
42
|
+
/** Whether a save from any session is still out. */
|
|
43
|
+
busy: () => boolean;
|
|
44
|
+
/** The in-flight save, settled or not — chain after it to stay in order. */
|
|
45
|
+
inFlight: () => Promise<void> | null;
|
|
46
|
+
/**
|
|
47
|
+
* Track a save sent under `token`. `onResult` runs only if that session is
|
|
48
|
+
* still current when it settles; otherwise `onStale` does (after a stale
|
|
49
|
+
* rejection has reached `onError`).
|
|
50
|
+
*/
|
|
51
|
+
track: (save: Promise<EditOutcome>, token: number, onResult: (result: SaveResult) => void, onStale?: (result: SaveResult) => void) => void;
|
|
52
|
+
/** Run `fn` once no save is out — dropped if this session ends first. */
|
|
53
|
+
whenIdle: (fn: () => void) => void;
|
|
54
|
+
}
|
|
55
|
+
export declare function useEditSession(options: EditSessionOptions): EditSession;
|
package/dist/types/index.d.ts
CHANGED
|
@@ -21,9 +21,11 @@ export { Skeleton, SkeletonText, LoadingOverlay, type SkeletonProps, type Skelet
|
|
|
21
21
|
export { ErrorBoundary, type ErrorBoundaryProps } from "./components/ErrorBoundary";
|
|
22
22
|
export { Field, FieldSet, type FieldProps, type FieldSetProps } from "./components/Field";
|
|
23
23
|
export { Input, Textarea, type InputProps, type TextareaProps } from "./components/Input";
|
|
24
|
-
export { InlineEdit, type InlineEditProps
|
|
25
|
-
export { EditGutter, type EditGutterProps
|
|
24
|
+
export { InlineEdit, type InlineEditProps } from "./components/InlineEdit";
|
|
25
|
+
export { EditGutter, type EditGutterProps } from "./components/EditGutter";
|
|
26
26
|
export { InlineMarkdownEdit, type InlineMarkdownEditProps } from "./components/InlineMarkdownEdit";
|
|
27
|
+
export type { EditActivation } from "./components/edit/activation";
|
|
28
|
+
export type { EditCommit, EditOutcome, EditRefusal } from "./components/edit/outcome";
|
|
27
29
|
export { SecretField, type SecretFieldProps, type SecretValue } from "./components/SecretField";
|
|
28
30
|
export { SettingRow, SettingGroup, type SettingRowProps, type SettingGroupProps, type SettingRowControlRenderProps, } from "./components/SettingRow";
|
|
29
31
|
export { DescriptionList, DescriptionListItem, type DescriptionListProps, type DescriptionListItemProps, type DescriptionListEntry, } from "./components/DescriptionList";
|