@protobase/ui 0.53.2 → 0.53.4
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/app/change-confirm/asks-to-confirm.js +10 -0
- package/dist/app/change-confirm/messages.js +16 -0
- package/dist/app/change-confirm/use-change-confirm.js +51 -0
- package/dist/app/field-editor.js +15 -6
- package/dist/app/list-edit/cell-field-editor.js +3 -1
- package/dist/app/list-page.js +1 -0
- package/dist/app-shell/page-header.js +34 -10
- package/dist/package.js +1 -1
- package/dist/shell/assets/app-KfucDRFg.js +9 -0
- package/dist/shell/assets/{index-CymocR26.css → index-DFC2GwM_.css} +1 -1
- package/dist/shell/assets/{index-mSmy9tWo.js → index-DITOhzRs.js} +2 -2
- package/dist/shell/assets/{records-map-DjwjFrSm.js → records-map-DLdvIE4o.js} +2 -2
- package/dist/shell/assets/{shared-protobase-ui-C1T4-PzP.js → shared-protobase-ui-Ss_2gqqM.js} +2 -2
- package/dist/shell/index.html +4 -4
- package/dist/shell/protobase.shell.json +1 -1
- package/dist/shell/protobase.sw.js +2 -2
- package/dist/shell-source-maps/assets/app-KfucDRFg.js.map +1 -0
- package/dist/shell-source-maps/assets/{index-mSmy9tWo.js.map → index-DITOhzRs.js.map} +1 -1
- package/dist/shell-source-maps/assets/{records-map-DjwjFrSm.js.map → records-map-DLdvIE4o.js.map} +1 -1
- package/dist/shell-source-maps/assets/{shared-protobase-ui-C1T4-PzP.js.map → shared-protobase-ui-Ss_2gqqM.js.map} +1 -1
- package/package.json +4 -4
- package/dist/shell/assets/app-CEBzuqJd.js +0 -9
- package/dist/shell-source-maps/assets/app-CEBzuqJd.js.map +0 -1
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { differs } from "../../live/field-values.js";
|
|
2
|
+
//#region src/app/change-confirm/asks-to-confirm.ts
|
|
3
|
+
const draftOf = (next) => typeof next === "object" && next !== null && "key" in next ? next.key : next;
|
|
4
|
+
/**
|
|
5
|
+
* Whether a change to a field with `.confirm()` asks first: when it moves the value away from the stored one while the
|
|
6
|
+
* value on screen is still that one, so a text asks at its first key and not at every one after.
|
|
7
|
+
*/
|
|
8
|
+
const asksToConfirm = (field, hints, value, stored, next) => hints?.confirm !== void 0 && !differs(field, value, stored) && differs(field, draftOf(next), stored);
|
|
9
|
+
//#endregion
|
|
10
|
+
export { asksToConfirm };
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { defineMessages } from "../../locale/messages.js";
|
|
2
|
+
//#region src/app/change-confirm/messages.ts
|
|
3
|
+
const text = defineMessages({
|
|
4
|
+
en: {
|
|
5
|
+
title: (label) => `Change ${label}?`,
|
|
6
|
+
change: "Change",
|
|
7
|
+
cancel: "Cancel"
|
|
8
|
+
},
|
|
9
|
+
nl: {
|
|
10
|
+
title: (label) => `${label} wijzigen?`,
|
|
11
|
+
change: "Wijzigen",
|
|
12
|
+
cancel: "Annuleren"
|
|
13
|
+
}
|
|
14
|
+
});
|
|
15
|
+
//#endregion
|
|
16
|
+
export { text };
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { Button } from "../../primitives/button.js";
|
|
2
|
+
import { Dialog } from "../../primitives/dialog.js";
|
|
3
|
+
import { humanize } from "../../live/naming.js";
|
|
4
|
+
import { asksToConfirm } from "./asks-to-confirm.js";
|
|
5
|
+
import { text } from "./messages.js";
|
|
6
|
+
import { useState } from "react";
|
|
7
|
+
import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
|
|
8
|
+
import { createPortal } from "react-dom";
|
|
9
|
+
//#region src/app/change-confirm/use-change-confirm.tsx
|
|
10
|
+
/**
|
|
11
|
+
* Whether `node` sits in the question a change asks. The dialog is portalled to the body, so a list cell does not clip
|
|
12
|
+
* it, while its events still bubble through the editor that asked it.
|
|
13
|
+
*/
|
|
14
|
+
const inChangeConfirm = (node) => node instanceof Element && node.closest("[data-change-confirm]") !== null;
|
|
15
|
+
/**
|
|
16
|
+
* The field's `onChange`, asking the view's `.confirm()` question before it hands on a change that `asksToConfirm`
|
|
17
|
+
* picks out, and the dialog that asks it. Cancelling drops the change, so the field keeps its value.
|
|
18
|
+
*/
|
|
19
|
+
const useChangeConfirm = ({ field, hints, value, stored, onChange }) => {
|
|
20
|
+
const [pending, setPending] = useState();
|
|
21
|
+
const change = (next) => {
|
|
22
|
+
if (asksToConfirm(field, hints, value, stored, next)) setPending({ next });
|
|
23
|
+
else onChange(next);
|
|
24
|
+
};
|
|
25
|
+
const cancel = () => setPending(void 0);
|
|
26
|
+
const confirm = () => {
|
|
27
|
+
setPending(void 0);
|
|
28
|
+
onChange(pending.next);
|
|
29
|
+
};
|
|
30
|
+
return {
|
|
31
|
+
onChange: change,
|
|
32
|
+
dialog: pending && createPortal(/* @__PURE__ */ jsx("div", {
|
|
33
|
+
"data-change-confirm": "",
|
|
34
|
+
children: /* @__PURE__ */ jsx(Dialog, {
|
|
35
|
+
title: text("title", hints?.label ?? humanize(field.name)),
|
|
36
|
+
description: hints?.confirm,
|
|
37
|
+
onClose: cancel,
|
|
38
|
+
actions: /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx(Button, {
|
|
39
|
+
onClick: cancel,
|
|
40
|
+
children: text("cancel")
|
|
41
|
+
}), /* @__PURE__ */ jsx(Button, {
|
|
42
|
+
variant: "primary",
|
|
43
|
+
onClick: confirm,
|
|
44
|
+
children: text("change")
|
|
45
|
+
})] })
|
|
46
|
+
})
|
|
47
|
+
}), document.body)
|
|
48
|
+
};
|
|
49
|
+
};
|
|
50
|
+
//#endregion
|
|
51
|
+
export { inChangeConfirm, useChangeConfirm };
|
package/dist/app/field-editor.js
CHANGED
|
@@ -20,8 +20,9 @@ import { Markdown } from "../markdown/markdown.js";
|
|
|
20
20
|
import { MarkdownEditor } from "../markdown/markdown-editor.js";
|
|
21
21
|
import { numeric, numericInputProps } from "./numeric-input.js";
|
|
22
22
|
import { ContactLink } from "../live/contact-link.js";
|
|
23
|
+
import { useChangeConfirm } from "./change-confirm/use-change-confirm.js";
|
|
23
24
|
import { ArrowUpRight } from "lucide-react";
|
|
24
|
-
import { jsx, jsxs } from "react/jsx-runtime";
|
|
25
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
25
26
|
import { enumLabel } from "@protobase/schema";
|
|
26
27
|
//#region src/app/field-editor.tsx
|
|
27
28
|
const BuiltInEditor = ({ field, hints, value, stored, relationLabel, relationTo, relationPicker, invalid, locked, placeholder, reveal, resource, onChange }) => {
|
|
@@ -150,11 +151,7 @@ const BuiltInEditor = ({ field, hints, value, stored, relationLabel, relationTo,
|
|
|
150
151
|
onChange: (event) => onChange(event.target.value)
|
|
151
152
|
});
|
|
152
153
|
};
|
|
153
|
-
|
|
154
|
-
* A field on a form: its field type's editor when the view chooses one that has an editor and the field may change,
|
|
155
|
-
* its display when it may not, and the built-in input or value otherwise, and when the field type throws.
|
|
156
|
-
*/
|
|
157
|
-
const FieldEditor = (props) => {
|
|
154
|
+
const ChosenEditor = (props) => {
|
|
158
155
|
const { field, hints, value, stored, invalid, locked, onChange } = props;
|
|
159
156
|
const shown = useShownWidget(field, hints);
|
|
160
157
|
const builtIn = /* @__PURE__ */ jsx(BuiltInEditor, { ...props });
|
|
@@ -177,5 +174,17 @@ const FieldEditor = (props) => {
|
|
|
177
174
|
fallback: builtIn
|
|
178
175
|
});
|
|
179
176
|
};
|
|
177
|
+
/**
|
|
178
|
+
* A field on a form: its field type's editor when the view chooses one that has an editor and the field may change,
|
|
179
|
+
* its display when it may not, and the built-in input or value otherwise, and when the field type throws. A field with
|
|
180
|
+
* `.confirm()` asks its question before its value first changes.
|
|
181
|
+
*/
|
|
182
|
+
const FieldEditor = (props) => {
|
|
183
|
+
const confirm = useChangeConfirm(props);
|
|
184
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(ChosenEditor, {
|
|
185
|
+
...props,
|
|
186
|
+
onChange: confirm.onChange
|
|
187
|
+
}), confirm.dialog] });
|
|
188
|
+
};
|
|
180
189
|
//#endregion
|
|
181
190
|
export { FieldEditor };
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { differs, toFieldDraft, toPatchValue } from "../../live/field-values.js";
|
|
2
|
+
import { inChangeConfirm } from "../change-confirm/use-change-confirm.js";
|
|
2
3
|
import { FieldEditor } from "../field-editor.js";
|
|
3
4
|
import { useEffect, useRef, useState } from "react";
|
|
4
5
|
import { jsx } from "react/jsx-runtime";
|
|
@@ -23,6 +24,7 @@ const CellFieldEditor = ({ field, hints, resource, stored, onSave, close }) => {
|
|
|
23
24
|
close(move);
|
|
24
25
|
};
|
|
25
26
|
const onKeyDown = (event) => {
|
|
27
|
+
if (inChangeConfirm(event.target)) return;
|
|
26
28
|
if (event.key === "Enter") {
|
|
27
29
|
event.preventDefault();
|
|
28
30
|
finish(true);
|
|
@@ -36,7 +38,7 @@ const CellFieldEditor = ({ field, hints, resource, stored, onSave, close }) => {
|
|
|
36
38
|
}
|
|
37
39
|
};
|
|
38
40
|
const onBlur = (event) => {
|
|
39
|
-
if (!ref.current?.contains(event.relatedTarget)) finish(true);
|
|
41
|
+
if (!ref.current?.contains(event.relatedTarget) && !inChangeConfirm(event.relatedTarget)) finish(true);
|
|
40
42
|
};
|
|
41
43
|
const onChange = (next) => {
|
|
42
44
|
setDraft(next);
|
package/dist/app/list-page.js
CHANGED
|
@@ -492,6 +492,7 @@ const LoadedList = ({ model, address, scope, heading }) => {
|
|
|
492
492
|
/* @__PURE__ */ jsx(PageHeader, {
|
|
493
493
|
title: heading?.title ?? plural,
|
|
494
494
|
subtitle: heading ? heading.subtitle : view?.help,
|
|
495
|
+
subtitleBelow: true,
|
|
495
496
|
actions: /* @__PURE__ */ jsxs(Fragment$1, { children: [
|
|
496
497
|
displaySwitch(!desktop),
|
|
497
498
|
sortButton,
|
|
@@ -1,19 +1,43 @@
|
|
|
1
1
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
2
2
|
//#region src/app-shell/page-header.tsx
|
|
3
|
-
const PageHeader = ({ title, subtitle, actions }) =>
|
|
4
|
-
|
|
3
|
+
const PageHeader = ({ title, subtitle, actions, subtitleBelow }) => {
|
|
4
|
+
if (subtitleBelow) return /* @__PURE__ */ jsx(StackedPageHeader, {
|
|
5
|
+
title,
|
|
6
|
+
subtitle,
|
|
7
|
+
actions
|
|
8
|
+
});
|
|
9
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
10
|
+
className: "flex flex-col gap-x-4 gap-y-2 md:flex-row md:items-end md:justify-between",
|
|
11
|
+
children: [/* @__PURE__ */ jsxs("div", {
|
|
12
|
+
className: "min-w-0 break-words md:flex-1",
|
|
13
|
+
children: [/* @__PURE__ */ jsx("h1", {
|
|
14
|
+
className: "text-xl font-semibold tracking-tight",
|
|
15
|
+
children: title
|
|
16
|
+
}), subtitle && /* @__PURE__ */ jsx("p", {
|
|
17
|
+
className: "text-muted-foreground",
|
|
18
|
+
children: subtitle
|
|
19
|
+
})]
|
|
20
|
+
}), actions && /* @__PURE__ */ jsx("div", {
|
|
21
|
+
className: "flex flex-wrap items-center gap-2 md:justify-end",
|
|
22
|
+
children: actions
|
|
23
|
+
})]
|
|
24
|
+
});
|
|
25
|
+
};
|
|
26
|
+
/** The title and actions in one row, which wraps the actions below the title when it runs out of room, then the subtitle. */
|
|
27
|
+
const StackedPageHeader = ({ title, subtitle, actions }) => /* @__PURE__ */ jsxs("div", {
|
|
28
|
+
className: "flex flex-col gap-2",
|
|
5
29
|
children: [/* @__PURE__ */ jsxs("div", {
|
|
6
|
-
className: "
|
|
30
|
+
className: "flex flex-wrap items-center justify-between gap-x-4 gap-y-2 md:flex-nowrap",
|
|
7
31
|
children: [/* @__PURE__ */ jsx("h1", {
|
|
8
|
-
className: "text-xl font-semibold tracking-tight",
|
|
32
|
+
className: "min-w-0 break-words text-xl font-semibold tracking-tight md:flex-1",
|
|
9
33
|
children: title
|
|
10
|
-
}),
|
|
11
|
-
className: "
|
|
12
|
-
children:
|
|
34
|
+
}), actions && /* @__PURE__ */ jsx("div", {
|
|
35
|
+
className: "flex flex-wrap items-center gap-2 md:justify-end",
|
|
36
|
+
children: actions
|
|
13
37
|
})]
|
|
14
|
-
}),
|
|
15
|
-
className: "
|
|
16
|
-
children:
|
|
38
|
+
}), subtitle && /* @__PURE__ */ jsx("p", {
|
|
39
|
+
className: "break-words text-muted-foreground",
|
|
40
|
+
children: subtitle
|
|
17
41
|
})]
|
|
18
42
|
});
|
|
19
43
|
//#endregion
|
package/dist/package.js
CHANGED