@protobase/ui 0.19.8 → 0.19.10
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/app.js +4 -4
- package/dist/app/create/create-offered.js +9 -0
- package/dist/app/create/create-page.js +4 -4
- package/dist/app/create/create-record-form.js +26 -62
- package/dist/app/create/create-route.js +29 -0
- package/dist/app/create/form.d.ts +30 -0
- package/dist/app/create/form.js +72 -0
- package/dist/app/custom-section.js +1 -1
- package/dist/app/displays/map-display.js +1 -1
- package/dist/app/displays/record-card.js +18 -4
- package/dist/app/displays/record-link.js +1 -1
- package/dist/app/displays/use-map-records.js +2 -2
- package/dist/app/embedded-list/embedded-columns.js +2 -2
- package/dist/app/embedded-list/embedded-list.js +5 -5
- package/dist/app/embedded-list/use-embedded-rows.js +2 -2
- package/dist/app/field-editor.js +21 -5
- package/dist/app/gallery/gallery-section.js +1 -1
- package/dist/app/gallery/photo-details.js +2 -2
- package/dist/app/list-page.js +18 -17
- package/dist/app/pages/blocks/card-row.js +2 -2
- package/dist/app/pages/blocks/field.js +2 -2
- package/dist/app/pages/blocks/link.js +1 -1
- package/dist/app/pages/blocks/modal-form.js +6 -6
- package/dist/app/pages/blocks/progress.js +1 -1
- package/dist/app/pages/blocks/record-card.js +2 -2
- package/dist/app/pages/blocks/record-row.js +1 -1
- package/dist/app/pages/blocks/show.js +2 -2
- package/dist/app/pages/blocks/stat.js +3 -3
- package/dist/app/pages/blocks/table.js +1 -1
- package/dist/app/pages/field-value.js +5 -2
- package/dist/app/pages/record-actions.js +2 -2
- package/dist/app/pages/use-run-action.js +2 -2
- package/dist/app/record-page.js +15 -11
- package/dist/app/record-row/record-row-body.js +2 -2
- package/dist/app/record-row/saved-record-row.js +1 -1
- package/dist/app/related-section.js +1 -1
- package/dist/app/searches/saved-search-rows.js +1 -1
- package/dist/app/searches/saved-search-section.js +2 -2
- package/dist/app/searches/saved-searches.js +2 -2
- package/dist/app/searches/search-controls.js +1 -1
- package/dist/app/searches/search-name-dialog.js +1 -1
- package/dist/app/searches/update-search-dialog.js +2 -2
- package/dist/app/searches/use-filter-chips.js +1 -1
- package/dist/app/searches/use-save-search.js +2 -2
- package/dist/app/searches/use-saved-searches.js +1 -1
- package/dist/app/searches/use-search-row.js +1 -1
- package/dist/app/sensitive-field.js +1 -1
- package/dist/app/use-app-address.js +1 -1
- package/dist/app/value-badge.js +1 -1
- package/dist/auth/sign-in-prompt.js +1 -1
- package/dist/data-grid/cells.js +1 -1
- package/dist/index.d.ts +9 -1
- package/dist/index.js +10 -2
- package/dist/live/columns-from-model.js +3 -0
- package/dist/live/grid-loader.js +1 -1
- package/dist/live/use-filter-config.js +1 -1
- package/dist/markdown/markdown-content.js +18 -0
- package/dist/markdown/markdown-editor.js +46 -0
- package/dist/markdown/markdown-excerpt.js +36 -0
- package/dist/markdown/markdown-hint.js +5 -0
- package/dist/markdown/markdown-html.js +59 -0
- package/dist/markdown/markdown.js +15 -0
- package/dist/package.js +1 -1
- package/dist/primitives/choices.d.ts +22 -0
- package/dist/primitives/choices.js +33 -0
- package/dist/primitives/dialog.d.ts +15 -0
- package/dist/primitives/menu-select.d.ts +14 -0
- package/dist/primitives/segmented.d.ts +23 -0
- package/dist/primitives/textarea.d.ts +9 -0
- package/dist/record-view/field.d.ts +27 -0
- package/dist/record-view/section.d.ts +10 -0
- package/dist/shell/assets/app-GKl91FLS.js +69 -0
- package/dist/shell/assets/{index-cMmFPuUQ.js → index-BB2BN-6s.js} +1 -1
- package/dist/shell/assets/index-DxV5Rp_F.css +2 -0
- package/dist/shell/assets/{maplibre-9XYrB9k6.js → maplibre-DHotMteL.js} +2 -2
- package/dist/shell/assets/markdown-content-C4zE1qZp.js +67 -0
- package/dist/shell/assets/{record-map-OBtKCiUB.js → record-map-BBbVY6vn.js} +1 -1
- package/dist/shell/assets/{records-map-C4oMPL5C.js → records-map-ur6HR-I5.js} +1 -1
- package/dist/shell/assets/shared-protobase-ui-D7kkYiTL.js +1 -0
- package/dist/shell/index.html +4 -4
- package/dist/shell/protobase.shell.json +1 -1
- package/package.json +6 -4
- package/dist/shell/assets/app-D8i5r_6U.js +0 -68
- package/dist/shell/assets/index-Cuulz26c.css +0 -2
- package/dist/shell/assets/shared-protobase-ui-CN1HmyDa.js +0 -1
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
//#region src/markdown/markdown-excerpt.ts
|
|
2
|
+
const blockMarks = [
|
|
3
|
+
[/^\s*(```|~~~).*$/, ""],
|
|
4
|
+
[/^\s*\[[^\]]+\]:\s+\S.*$/, ""],
|
|
5
|
+
[/^\s*([-*_]\s*){3,}$/, ""],
|
|
6
|
+
[/^\s*\|?(\s*:?-+:?\s*\|)+\s*:?-*:?\s*$/, ""],
|
|
7
|
+
[/^\s*#{1,6}\s+/, ""],
|
|
8
|
+
[/^\s*(>\s?)+/, ""],
|
|
9
|
+
[/^\s*([-*+]|\d+[.)])\s+(\[[ xX]\]\s+)?/, ""]
|
|
10
|
+
];
|
|
11
|
+
const inlineMarks = [
|
|
12
|
+
[/!\[([^\]]*)\]\([^)]*\)/g, "$1"],
|
|
13
|
+
[/\[([^\]]*)\]\([^)]*\)/g, "$1"],
|
|
14
|
+
[/\[([^\]]+)\]\[[^\]]*\]/g, "$1"],
|
|
15
|
+
[/<((?:https?|mailto):[^>\s]+)>/gi, "$1"],
|
|
16
|
+
[/<\/?[a-z][^>]*>/gi, ""],
|
|
17
|
+
[/`([^`]+)`/g, "$1"],
|
|
18
|
+
[/(?<!\\)(\*\*|__)(?=\S)(.+?)(?<=\S)\1/g, "$2"],
|
|
19
|
+
[/(?<![\\\w*])\*(?=\S)(.+?)(?<=\S)\*/g, "$1"],
|
|
20
|
+
[/(?<![\\\w])_(?=\S)(.+?)(?<=\S)_(?!\w)/g, "$1"],
|
|
21
|
+
[/(?<!\\)~~(.+?)~~/g, "$1"],
|
|
22
|
+
[/\s*\|\s*/g, " "],
|
|
23
|
+
[/\\([\\`*_{}[\]()#+\-.!>~|])/g, "$1"]
|
|
24
|
+
];
|
|
25
|
+
const longest = 200;
|
|
26
|
+
/**
|
|
27
|
+
* Markdown as one line of plain text, as a list's cell shows it: the text of its headings, lists, links and emphasis
|
|
28
|
+
* without their marks, at most 200 characters.
|
|
29
|
+
*/
|
|
30
|
+
const markdownExcerpt = (source) => {
|
|
31
|
+
const lines = source.split(/\r?\n/).map((line) => blockMarks.reduce((text, [mark, kept]) => text.replace(mark, kept), line));
|
|
32
|
+
const text = inlineMarks.reduce((joined, [mark, kept]) => joined.replace(mark, kept), lines.join("\n")).replace(/\s+/g, " ").trim();
|
|
33
|
+
return text.length > longest ? `${text.slice(0, 199).trimEnd()}…` : text;
|
|
34
|
+
};
|
|
35
|
+
//#endregion
|
|
36
|
+
export { markdownExcerpt };
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { Marked } from "marked";
|
|
2
|
+
//#region src/markdown/markdown-html.ts
|
|
3
|
+
const escapes = {
|
|
4
|
+
"&": "&",
|
|
5
|
+
"<": "<",
|
|
6
|
+
">": ">",
|
|
7
|
+
"\"": """,
|
|
8
|
+
"'": "'"
|
|
9
|
+
};
|
|
10
|
+
const escapeHtml = (text) => text.replace(/[&<>"']/g, (char) => escapes[char]);
|
|
11
|
+
const scheme = /^([a-z][a-z0-9+.-]*):/i;
|
|
12
|
+
const linkSchemes = /* @__PURE__ */ new Set([
|
|
13
|
+
"http",
|
|
14
|
+
"https",
|
|
15
|
+
"mailto",
|
|
16
|
+
"tel"
|
|
17
|
+
]);
|
|
18
|
+
const imageSchemes = /* @__PURE__ */ new Set(["http", "https"]);
|
|
19
|
+
const ignoredByBrowsers = /[\u0000-\u0020\u007f]/g;
|
|
20
|
+
const safeAddress = (href, schemes) => {
|
|
21
|
+
const named = scheme.exec(href.replace(ignoredByBrowsers, ""))?.[1]?.toLowerCase();
|
|
22
|
+
if (named !== void 0 && !schemes.has(named)) return null;
|
|
23
|
+
try {
|
|
24
|
+
return encodeURI(href).replace(/%25/g, "%");
|
|
25
|
+
} catch (error) {
|
|
26
|
+
if (error instanceof URIError) return null;
|
|
27
|
+
throw error;
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
const titled = (title) => title ? ` title="${escapeHtml(title)}"` : "";
|
|
31
|
+
const parser = ({ inLink = false }) => new Marked({
|
|
32
|
+
gfm: true,
|
|
33
|
+
breaks: true,
|
|
34
|
+
renderer: {
|
|
35
|
+
html: ({ text, block }) => block ? `<p>${escapeHtml(text)}</p>` : escapeHtml(text),
|
|
36
|
+
link({ href, title, tokens }) {
|
|
37
|
+
const text = this.parser.parseInline(tokens);
|
|
38
|
+
const address = inLink ? null : safeAddress(href, linkSchemes);
|
|
39
|
+
if (address === null) return text;
|
|
40
|
+
return `<a href="${escapeHtml(address)}"${titled(title)} target="_blank" rel="noopener noreferrer nofollow">${text}</a>`;
|
|
41
|
+
},
|
|
42
|
+
image({ href, title, text }) {
|
|
43
|
+
const address = inLink ? null : safeAddress(href, imageSchemes);
|
|
44
|
+
if (address === null) return escapeHtml(text);
|
|
45
|
+
return `<img src="${escapeHtml(address)}" alt="${escapeHtml(text)}"${titled(title)} loading="lazy">`;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
const parsers = {
|
|
50
|
+
inLink: parser({ inLink: true }),
|
|
51
|
+
page: parser({})
|
|
52
|
+
};
|
|
53
|
+
/**
|
|
54
|
+
* Markdown as HTML (GitHub's flavour, line breaks kept): raw HTML escaped, links and images only to http(s), mailto and
|
|
55
|
+
* tel addresses or relative ones, and links opening in a new tab. The page sanitises the result again before showing it.
|
|
56
|
+
*/
|
|
57
|
+
const markdownHtml = (source, options = {}) => (options.inLink ? parsers.inLink : parsers.page).parse(source, { async: false });
|
|
58
|
+
//#endregion
|
|
59
|
+
export { markdownHtml };
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { cn } from "../lib/cn.js";
|
|
2
|
+
import { Suspense, lazy } from "react";
|
|
3
|
+
import { jsx } from "react/jsx-runtime";
|
|
4
|
+
//#region src/markdown/markdown.tsx
|
|
5
|
+
const MarkdownContent = lazy(() => import("./markdown-content.js").then((module) => ({ default: module.MarkdownContent })));
|
|
6
|
+
/** Markdown rendered and sanitised; its source, line breaks kept, until the renderer has loaded. */
|
|
7
|
+
const Markdown = (props) => /* @__PURE__ */ jsx(Suspense, {
|
|
8
|
+
fallback: /* @__PURE__ */ jsx("div", {
|
|
9
|
+
className: cn("whitespace-pre-line break-words", props.className),
|
|
10
|
+
children: props.source
|
|
11
|
+
}),
|
|
12
|
+
children: /* @__PURE__ */ jsx(MarkdownContent, { ...props })
|
|
13
|
+
});
|
|
14
|
+
//#endregion
|
|
15
|
+
export { Markdown };
|
package/dist/package.js
CHANGED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { ReactNode } from "react";
|
|
2
|
+
//#region src/primitives/choices.d.ts
|
|
3
|
+
export type ChoiceOption<T extends string> = {
|
|
4
|
+
value: T;
|
|
5
|
+
label: string;
|
|
6
|
+
description?: ReactNode;
|
|
7
|
+
disabled?: boolean;
|
|
8
|
+
};
|
|
9
|
+
/**
|
|
10
|
+
* `name` groups the radio buttons, so that the arrow keys move between them; `label` names the group for screen
|
|
11
|
+
* readers. A disabled option shows and cannot be chosen.
|
|
12
|
+
*/
|
|
13
|
+
export type ChoicesProps<T extends string> = {
|
|
14
|
+
name: string;
|
|
15
|
+
label?: string;
|
|
16
|
+
options: Array<ChoiceOption<T>>;
|
|
17
|
+
value: T | undefined;
|
|
18
|
+
onChange: (value: T) => void;
|
|
19
|
+
};
|
|
20
|
+
/** One of a few options, each a card of its own row with a line that says what it means; the chosen one is outlined. */
|
|
21
|
+
export declare const Choices: <T extends string>({ name, label, options, value, onChange }: ChoicesProps<T>) => import("react").JSX.Element;
|
|
22
|
+
//#endregion
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { cn } from "../lib/cn.js";
|
|
2
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
//#region src/primitives/choices.tsx
|
|
4
|
+
/** One of a few options, each a card of its own row with a line that says what it means; the chosen one is outlined. */
|
|
5
|
+
const Choices = ({ name, label, options, value, onChange }) => /* @__PURE__ */ jsx("div", {
|
|
6
|
+
role: "radiogroup",
|
|
7
|
+
"aria-label": label,
|
|
8
|
+
className: "flex flex-col gap-2",
|
|
9
|
+
children: options.map((option) => /* @__PURE__ */ jsxs("label", {
|
|
10
|
+
className: cn("block rounded-lg border p-3 transition-colors has-[:focus-visible]:outline-2 has-[:focus-visible]:outline-offset-2 has-[:focus-visible]:outline-primary", option.disabled ? "cursor-default bg-background opacity-60" : option.value === value ? "cursor-pointer border-primary bg-primary-soft" : "cursor-pointer bg-background hover:bg-surface"),
|
|
11
|
+
children: [
|
|
12
|
+
/* @__PURE__ */ jsx("input", {
|
|
13
|
+
type: "radio",
|
|
14
|
+
name,
|
|
15
|
+
value: option.value,
|
|
16
|
+
checked: option.value === value,
|
|
17
|
+
disabled: option.disabled,
|
|
18
|
+
onChange: () => onChange(option.value),
|
|
19
|
+
className: "sr-only"
|
|
20
|
+
}),
|
|
21
|
+
/* @__PURE__ */ jsx("span", {
|
|
22
|
+
className: cn("block font-medium", option.value === value && "text-primary-text"),
|
|
23
|
+
children: option.label
|
|
24
|
+
}),
|
|
25
|
+
option.description && /* @__PURE__ */ jsx("span", {
|
|
26
|
+
className: "mt-0.5 block text-xs leading-snug text-muted-foreground",
|
|
27
|
+
children: option.description
|
|
28
|
+
})
|
|
29
|
+
]
|
|
30
|
+
}, option.value))
|
|
31
|
+
});
|
|
32
|
+
//#endregion
|
|
33
|
+
export { Choices };
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { ReactNode } from "react";
|
|
2
|
+
//#region src/primitives/dialog.d.ts
|
|
3
|
+
export type DialogProps = {
|
|
4
|
+
title: string;
|
|
5
|
+
description?: ReactNode;
|
|
6
|
+
onClose: () => void;
|
|
7
|
+
/** Buttons, rendered right-aligned. */
|
|
8
|
+
actions: ReactNode;
|
|
9
|
+
children?: ReactNode;
|
|
10
|
+
/** Room for a row of cards or a table in the body, instead of a short question. */
|
|
11
|
+
wide?: boolean;
|
|
12
|
+
};
|
|
13
|
+
/** A modal with a title, optional body and action buttons. Closes on Escape and on a click outside. */
|
|
14
|
+
export declare const Dialog: ({ title, description, onClose, actions, children, wide }: DialogProps) => import("react").JSX.Element;
|
|
15
|
+
//#endregion
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { SegmentedOption } from "./segmented.js";
|
|
2
|
+
//#region src/primitives/menu-select.d.ts
|
|
3
|
+
export type MenuSelectProps<T extends string> = {
|
|
4
|
+
value: T;
|
|
5
|
+
options: Array<SegmentedOption<T>>;
|
|
6
|
+
onChange: (value: T) => void;
|
|
7
|
+
label: string;
|
|
8
|
+
};
|
|
9
|
+
/**
|
|
10
|
+
* The choices of a Segmented switch in a menu, for a row without room for every option: a button with the chosen
|
|
11
|
+
* option's icon and label, opening the options under its right edge.
|
|
12
|
+
*/
|
|
13
|
+
export declare const MenuSelect: <T extends string>({ value, options, onChange, label }: MenuSelectProps<T>) => import("react").JSX.Element;
|
|
14
|
+
//#endregion
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { ComponentType } from "react";
|
|
2
|
+
//#region src/primitives/segmented.d.ts
|
|
3
|
+
export type SegmentedOption<T extends string> = {
|
|
4
|
+
value: T;
|
|
5
|
+
label: string;
|
|
6
|
+
icon?: ComponentType<{
|
|
7
|
+
className?: string;
|
|
8
|
+
}>;
|
|
9
|
+
};
|
|
10
|
+
/**
|
|
11
|
+
* `md` matches a medium Button, for a switch that sits among buttons. `iconOnly` draws each option as its icon alone,
|
|
12
|
+
* its label the button's name, for a row without room for the words.
|
|
13
|
+
*/
|
|
14
|
+
export type SegmentedProps<T extends string> = {
|
|
15
|
+
value: T;
|
|
16
|
+
options: Array<SegmentedOption<T>>;
|
|
17
|
+
onChange: (value: T) => void;
|
|
18
|
+
label?: string;
|
|
19
|
+
size?: 'sm' | 'md';
|
|
20
|
+
iconOnly?: boolean;
|
|
21
|
+
};
|
|
22
|
+
export declare const Segmented: <T extends string>({ value, options, onChange, label, size, iconOnly }: SegmentedProps<T>) => import("react").JSX.Element;
|
|
23
|
+
//#endregion
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { TextareaHTMLAttributes } from "react";
|
|
2
|
+
//#region src/primitives/textarea.d.ts
|
|
3
|
+
export type TextareaProps = Omit<TextareaHTMLAttributes<HTMLTextAreaElement>, 'value'> & {
|
|
4
|
+
value: string;
|
|
5
|
+
invalid?: boolean;
|
|
6
|
+
};
|
|
7
|
+
/** A text box of several lines that grows with its text instead of scrolling inside. */
|
|
8
|
+
export declare const Textarea: ({ invalid, className, rows, value, ...rest }: TextareaProps) => import("react").JSX.Element;
|
|
9
|
+
//#endregion
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { ReactNode } from "react";
|
|
2
|
+
//#region src/record-view/field.d.ts
|
|
3
|
+
export type FieldConflict = {
|
|
4
|
+
user: {
|
|
5
|
+
name: string;
|
|
6
|
+
initials: string;
|
|
7
|
+
hue: number;
|
|
8
|
+
};
|
|
9
|
+
ago: string;
|
|
10
|
+
theirValue: string;
|
|
11
|
+
onKeepMine: () => void;
|
|
12
|
+
onUseTheirs: () => void;
|
|
13
|
+
};
|
|
14
|
+
export type FieldProps = {
|
|
15
|
+
label: string;
|
|
16
|
+
/** Marks the label with an asterisk. */
|
|
17
|
+
required?: boolean;
|
|
18
|
+
help?: string;
|
|
19
|
+
example?: string;
|
|
20
|
+
error?: string;
|
|
21
|
+
conflict?: FieldConflict;
|
|
22
|
+
/** Takes the whole row of a grid of two columns, as a text of several lines does. */
|
|
23
|
+
wide?: boolean;
|
|
24
|
+
children: ReactNode;
|
|
25
|
+
};
|
|
26
|
+
export declare const Field: ({ label, required, help, example, error, conflict, wide, children }: FieldProps) => import("react").JSX.Element;
|
|
27
|
+
//#endregion
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { ReactNode } from "react";
|
|
2
|
+
//#region src/record-view/section.d.ts
|
|
3
|
+
/** A titled part of the record page; `actions` sit at the end of the title's line. */
|
|
4
|
+
export declare const Section: ({ title, help, actions, children }: {
|
|
5
|
+
title: string;
|
|
6
|
+
help?: string;
|
|
7
|
+
actions?: ReactNode;
|
|
8
|
+
children: ReactNode;
|
|
9
|
+
}) => import("react").JSX.Element;
|
|
10
|
+
//#endregion
|