@protobase/ui 0.23.2 → 0.23.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/app.js CHANGED
@@ -12,11 +12,12 @@ import { CustomBoundary } from "./pages/custom-boundary.js";
12
12
  import { PluginSourceProvider } from "../plugins/plugin-source.js";
13
13
  import { AuthGate } from "../auth/auth-gate.js";
14
14
  import { isAdmin, userToShell } from "../auth/user-to-shell.js";
15
+ import { sourceStatusOf } from "../data/with-sources.js";
16
+ import { useLiveUpdates } from "../data/use-live-updates.js";
15
17
  import { useOpenedRecord } from "../data/use-record.js";
16
18
  import { humanize } from "../live/naming.js";
17
19
  import { ToastProvider } from "../toasts/toast-provider.js";
18
20
  import { recordTitle } from "../live/model-helpers.js";
19
- import { sourceStatusOf } from "../data/with-sources.js";
20
21
  import { MetaGate, Notice, useAdminMeta } from "./meta-gate.js";
21
22
  import { useAppAddress } from "./use-app-address.js";
22
23
  import { documentTitle, titleParts, useDocumentTitle } from "./document-title.js";
@@ -75,6 +76,7 @@ const Routes = ({ workspace, sidebarMode, assistant, runtime }) => {
75
76
  resource: shown?.resource,
76
77
  key: shown?.key
77
78
  });
79
+ useLiveUpdates(meta);
78
80
  const groups = navFromMeta(meta, basePath, recent);
79
81
  const firstPage = Object.keys(meta.pages)[0];
80
82
  const firstResource = Object.keys(meta.resources)[0];
@@ -1,10 +1,10 @@
1
1
  import { Spinner } from "../primitives/spinner.js";
2
2
  import { ClientProvider, useClient, useSourceClients } from "../data/api-provider.js";
3
+ import { withSources } from "../data/with-sources.js";
3
4
  import { MetaContext } from "../data/meta-context.js";
4
5
  import { allowEverything } from "../data/meta-data.js";
5
6
  import { useMeta } from "../data/use-meta.js";
6
7
  import { useSourceMetas } from "../data/use-source-metas.js";
7
- import { withSources } from "../data/with-sources.js";
8
8
  import { useContext, useMemo } from "react";
9
9
  import { isApiError, routedClient } from "@protobase/client";
10
10
  import { jsx, jsxs } from "react/jsx-runtime";
@@ -0,0 +1,25 @@
1
+ //#region src/app/record-picker.d.ts
2
+ type One = {
3
+ multiple?: false;
4
+ /** The chosen record's key, as text; `undefined` for none. */
5
+ value: string | undefined;
6
+ onChange: (key: string) => void;
7
+ };
8
+ type Several = {
9
+ multiple: true;
10
+ /** The chosen records' keys, as text. */
11
+ value: string[];
12
+ onChange: (keys: string[]) => void;
13
+ };
14
+ export type RecordPickerProps = (One | Several) & {
15
+ /** The name of the resource whose records it picks. */
16
+ resource: string;
17
+ invalid?: boolean;
18
+ };
19
+ /**
20
+ * Picks records of a resource of the app with the create form's relation picker: type to narrow them by their search
21
+ * fields or title, scroll for more, choose with the mouse or the keyboard. With `multiple`, each choice is added as a
22
+ * token that can be taken off again, and choosing a chosen record takes it off.
23
+ */
24
+ export declare const RecordPicker: (props: RecordPickerProps) => import("react").JSX.Element;
25
+ //#endregion
@@ -0,0 +1,65 @@
1
+ import { useAdminMeta } from "./meta-gate.js";
2
+ import { RelationPicker } from "./relation-picker.js";
3
+ import { Token } from "../primitives/token.js";
4
+ import { usePickedLabels } from "./use-picked-labels.js";
5
+ import { useState } from "react";
6
+ import { jsx, jsxs } from "react/jsx-runtime";
7
+ //#region src/app/record-picker.tsx
8
+ /**
9
+ * Picks records of a resource of the app with the create form's relation picker: type to narrow them by their search
10
+ * fields or title, scroll for more, choose with the mouse or the keyboard. With `multiple`, each choice is added as a
11
+ * token that can be taken off again, and choosing a chosen record takes it off.
12
+ */
13
+ const RecordPicker = (props) => {
14
+ const { resources, views } = useAdminMeta();
15
+ const target = resources[props.resource];
16
+ if (!target) throw new Error(`RecordPicker: the app has no resource named "${props.resource}"`);
17
+ if (target.primaryKey.length !== 1) throw new Error(`RecordPicker: "${props.resource}" has a composite key, which a picker cannot name`);
18
+ const view = views[props.resource];
19
+ const keys = props.multiple ? props.value : props.value === void 0 ? [] : [props.value];
20
+ const [known, setKnown] = useState({});
21
+ const fetched = usePickedLabels(target, view, keys);
22
+ const labelOf = (key) => known[key] ?? fetched.get(key) ?? key;
23
+ const remember = (key, label) => setKnown((current) => ({
24
+ ...current,
25
+ [key]: label
26
+ }));
27
+ if (!props.multiple) {
28
+ const { value, onChange } = props;
29
+ return /* @__PURE__ */ jsx(RelationPicker, {
30
+ target,
31
+ view,
32
+ value: value ?? "",
33
+ label: value === void 0 ? void 0 : labelOf(value),
34
+ invalid: props.invalid,
35
+ onChange: (key, label) => {
36
+ remember(key, label);
37
+ onChange(key);
38
+ }
39
+ });
40
+ }
41
+ const { value, onChange } = props;
42
+ return /* @__PURE__ */ jsxs("div", {
43
+ className: "flex flex-col gap-2",
44
+ children: [/* @__PURE__ */ jsx(RelationPicker, {
45
+ target,
46
+ view,
47
+ value: "",
48
+ chosen: value,
49
+ several: true,
50
+ invalid: props.invalid,
51
+ onChange: (key, label) => {
52
+ remember(key, label);
53
+ onChange(value.includes(key) ? value.filter((chosen) => chosen !== key) : [...value, key]);
54
+ }
55
+ }), value.length > 0 && /* @__PURE__ */ jsx("div", {
56
+ className: "flex flex-wrap gap-1",
57
+ children: value.map((key) => /* @__PURE__ */ jsx(Token, {
58
+ label: labelOf(key),
59
+ onRemove: () => onChange(value.filter((chosen) => chosen !== key))
60
+ }, key))
61
+ })]
62
+ });
63
+ };
64
+ //#endregion
65
+ export { RecordPicker };
@@ -26,7 +26,7 @@ const useLoadMoreInView = (list, sentinel, loaded, loadMore) => {
26
26
  * Picks a record of another resource: type to narrow the records by their search fields or title, scroll for more, and
27
27
  * choose with the mouse or with the arrow keys and Enter; Escape closes the list.
28
28
  */
29
- const RelationPicker = ({ target, view, value, label, invalid, onChange }) => {
29
+ const RelationPicker = ({ target, view, value, label, chosen = [value], several = false, invalid, onChange }) => {
30
30
  const [text, setText] = useState("");
31
31
  const [open, setOpen] = useState(false);
32
32
  const [active, setActive] = useState(-1);
@@ -66,7 +66,9 @@ const RelationPicker = ({ target, view, value, label, invalid, onChange }) => {
66
66
  };
67
67
  const choose = (item) => {
68
68
  onChange(String(item[keyField]), String(item[labelField]));
69
- close();
69
+ if (!several) return close();
70
+ setText("");
71
+ setActive(-1);
70
72
  };
71
73
  const onKeyDown = (event) => {
72
74
  if (event.key === "ArrowDown" || event.key === "ArrowUp") {
@@ -111,6 +113,7 @@ const RelationPicker = ({ target, view, value, label, invalid, onChange }) => {
111
113
  ref: setList,
112
114
  id: listId,
113
115
  role: "listbox",
116
+ "aria-multiselectable": several || void 0,
114
117
  className: "absolute z-30 mt-1 max-h-60 w-full overflow-y-auto rounded-md border bg-background py-1 shadow-pop",
115
118
  onMouseDown: (event) => event.preventDefault(),
116
119
  children: [
@@ -118,7 +121,7 @@ const RelationPicker = ({ target, view, value, label, invalid, onChange }) => {
118
121
  id: optionId(index),
119
122
  role: "option",
120
123
  "aria-selected": index === active,
121
- className: cn("cursor-pointer truncate px-3 py-1.5", index === active && "bg-muted", String(item[keyField]) === value && "font-medium"),
124
+ className: cn("cursor-pointer truncate px-3 py-1.5", index === active && "bg-muted", chosen.includes(String(item[keyField])) && "font-medium"),
122
125
  onMouseMove: () => index !== active && setActive(index),
123
126
  onClick: () => choose(item),
124
127
  children: String(item[labelField])
@@ -0,0 +1,19 @@
1
+ import { keys } from "../data/query-keys.js";
2
+ import { useClient } from "../data/api-provider.js";
3
+ import { fetchLabels } from "../live/relation-labels.js";
4
+ import { keepPreviousData, useQuery, useQueryClient } from "@tanstack/react-query";
5
+ //#region src/app/use-picked-labels.ts
6
+ /** The names of the chosen records, fetched in one list call; the previous names stay while the next ones load. */
7
+ const usePickedLabels = (target, view, ids) => {
8
+ const client = useClient();
9
+ const queryClient = useQueryClient();
10
+ const sorted = [...new Set(ids)].sort();
11
+ return useQuery({
12
+ queryKey: keys.pickedLabels(target.name, sorted.join(",")),
13
+ enabled: sorted.length > 0,
14
+ placeholderData: keepPreviousData,
15
+ queryFn: () => fetchLabels(queryClient, client, target, sorted, view)
16
+ }).data ?? /* @__PURE__ */ new Map();
17
+ };
18
+ //#endregion
19
+ export { usePickedLabels };
@@ -56,6 +56,7 @@ export declare const useClient: () => {
56
56
  form: (name: string) => Promise<import("@protobase/client").PublicForm>;
57
57
  formChallenge: (name: string) => Promise<import("@protobase/client").FormChallenge>;
58
58
  submitForm: (name: string, values: Record<string, unknown>, proof: import("@protobase/client").FormProof) => Promise<void>;
59
+ live: (handlers: import("@protobase/client").LiveHandlers) => () => void;
59
60
  };
60
61
  /**
61
62
  * The client of the API that serves `resource`: a source's for a resource the app draws from one, the app's own
@@ -114,6 +115,7 @@ export declare const useResourceClient: (resource: string) => {
114
115
  form: (name: string) => Promise<import("@protobase/client").PublicForm>;
115
116
  formChallenge: (name: string) => Promise<import("@protobase/client").FormChallenge>;
116
117
  submitForm: (name: string, values: Record<string, unknown>, proof: import("@protobase/client").FormProof) => Promise<void>;
118
+ live: (handlers: import("@protobase/client").LiveHandlers) => () => void;
117
119
  };
118
120
  export type ApiProviderProps = {
119
121
  /** Defaults to `/api/v1` on the current origin. */
@@ -9,6 +9,8 @@ const createFocusRefresh = () => {
9
9
  let paused = 0;
10
10
  return {
11
11
  refetchOnWindowFocus: () => paused > 0 ? false : "always",
12
+ /** Whether a form on screen holds unsaved changes, so its record must not be read again under it. */
13
+ paused: () => paused > 0,
12
14
  /** Holds the refetches until the returned function is called. */
13
15
  pause: () => {
14
16
  paused += 1;
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Reads again what the page shows of the resources whose records changed: every query keyed by the resource, as after
3
+ * a write of the page's own. While a form holds unsaved changes (`paused`), the records stay as they are, only marked
4
+ * stale, so the form's Save still finds a conflict.
5
+ */
6
+ const liveRefresh = (queryClient, paused) => {
7
+ const pending = /* @__PURE__ */ new Set();
8
+ let timer;
9
+ const flush = () => {
10
+ timer = void 0;
11
+ const resources = new Set(pending);
12
+ pending.clear();
13
+ const shown = (key) => resources.has(key[1]);
14
+ queryClient.invalidateQueries({ predicate: (query) => shown(query.queryKey) && query.queryKey[0] !== "record" });
15
+ queryClient.invalidateQueries({
16
+ predicate: (query) => shown(query.queryKey) && query.queryKey[0] === "record",
17
+ refetchType: paused() ? "none" : "active"
18
+ });
19
+ };
20
+ return {
21
+ changed: (resources) => {
22
+ for (const resource of resources) pending.add(resource);
23
+ if (pending.size > 0 && !timer) timer = setTimeout(flush, 300);
24
+ },
25
+ stop: () => clearTimeout(timer)
26
+ };
27
+ };
28
+ //#endregion
29
+ export { liveRefresh };
@@ -126,6 +126,12 @@ const keys = {
126
126
  resource,
127
127
  ids
128
128
  ],
129
+ /** The names of the records a `RecordPicker` holds. */
130
+ pickedLabels: (resource, ids) => [
131
+ "picked-labels",
132
+ resource,
133
+ ids
134
+ ],
129
135
  count: (resource, filter) => [
130
136
  "count",
131
137
  resource,
@@ -0,0 +1,36 @@
1
+ import { FocusRefreshContext } from "./focus-refresh.js";
2
+ import { useClient } from "./api-provider.js";
3
+ import { liveRefresh } from "./live-refresh.js";
4
+ import { sourceStatusOf } from "./with-sources.js";
5
+ import { useContext, useEffect, useMemo } from "react";
6
+ import { useQueryClient } from "@tanstack/react-query";
7
+ //#region src/data/use-live-updates.ts
8
+ /**
9
+ * Keeps what the page shows of the app's `.live()` resources current: hears which records change (`client.live`) and
10
+ * reads again the lists, records and counts of their resources. Connects only when the caller reads a live resource.
11
+ */
12
+ const useLiveUpdates = (meta) => {
13
+ const client = useClient();
14
+ const queryClient = useQueryClient();
15
+ const focusRefresh = useContext(FocusRefreshContext);
16
+ const live = useMemo(() => Object.values(meta.resources).filter((model) => model.live && !sourceStatusOf(meta, model.name)).map((model) => model.name).sort().join(","), [meta]);
17
+ useEffect(() => {
18
+ if (!live) return;
19
+ const refresh = liveRefresh(queryClient, () => focusRefresh?.paused() ?? false);
20
+ const stop = client.live({
21
+ onChanges: (changes) => refresh.changed(changes.map((change) => change.resource)),
22
+ onResync: () => refresh.changed(live.split(","))
23
+ });
24
+ return () => {
25
+ stop();
26
+ refresh.stop();
27
+ };
28
+ }, [
29
+ client,
30
+ queryClient,
31
+ focusRefresh,
32
+ live
33
+ ]);
34
+ };
35
+ //#endregion
36
+ export { useLiveUpdates };
@@ -1,9 +1,10 @@
1
1
  import { cn } from "../lib/cn.js";
2
2
  import { formatInt } from "../format/number.js";
3
+ import { Token } from "../primitives/token.js";
3
4
  import { facetLabel } from "./filter-state.js";
4
5
  import { comboboxOptions, moveActive } from "./combobox-options.js";
5
6
  import { useEffect, useId, useRef, useState } from "react";
6
- import { Check, Search, X } from "lucide-react";
7
+ import { Check, Search } from "lucide-react";
7
8
  import { jsx, jsxs } from "react/jsx-runtime";
8
9
  //#region src/filter-panel/facet-combobox.tsx
9
10
  /** Chooses several values of a facet: the choices sit in the box as removable tokens, typing searches the options. */
@@ -50,19 +51,9 @@ const FacetCombobox = ({ config, selected, onToggle, inline = false }) => {
50
51
  onClick: () => input.current?.focus(),
51
52
  children: [
52
53
  /* @__PURE__ */ jsx(Search, { className: "size-3.5 shrink-0 text-muted-foreground" }),
53
- selected.map((value) => /* @__PURE__ */ jsxs("span", {
54
- className: "inline-flex h-5 max-w-full items-center gap-0.5 rounded bg-primary-soft pl-1.5 pr-0.5 text-xs font-medium text-primary-text",
55
- children: [/* @__PURE__ */ jsx("span", {
56
- className: "truncate",
57
- children: facetLabel(config, value)
58
- }), /* @__PURE__ */ jsx("button", {
59
- type: "button",
60
- "aria-label": `Remove ${facetLabel(config, value)}`,
61
- className: "rounded p-0.5 hover:bg-primary-border/60",
62
- onMouseDown: (event) => event.preventDefault(),
63
- onClick: () => onToggle(value),
64
- children: /* @__PURE__ */ jsx(X, { className: "size-3" })
65
- })]
54
+ selected.map((value) => /* @__PURE__ */ jsx(Token, {
55
+ label: facetLabel(config, value),
56
+ onRemove: () => onToggle(value)
66
57
  }, value)),
67
58
  /* @__PURE__ */ jsx("input", {
68
59
  ref: input,
package/dist/index.d.ts CHANGED
@@ -59,5 +59,6 @@ import { Field, FieldProps } from "./record-view/field.js";
59
59
  import { useFieldControl } from "./primitives/field-control.js";
60
60
  import { Section } from "./record-view/section.js";
61
61
  import { Form, FormProps } from "./app/create/form.js";
62
+ import { RecordPicker, RecordPickerProps } from "./app/record-picker.js";
62
63
  import { Spinner } from "./primitives/spinner.js";
63
- export { ActionCard, type ActionCardProps, type ActionContext, type ActionHandler, ActionInputError, type ActionInputProps, ApiProvider, type ApiProviderProps, App, AppLink, type AppProps, AssistantDock, type AssistantDockProps, AuthLayout, type AuthLayoutProps, type BackendData, type BackendRefresh, type BeforeSignInProps, Button, type ButtonProps, type CardAction, type CardTone, ChatMessage, ChatThread, type ChatThreadProps, type ChoiceOption, Choices, type ChoicesProps, CodeForm, type CodeFormProps, CompactTable, type CompactTableProps, Composer, type ComposerProps, type CurrentAddress, Dialog, type DialogProps, type DiffLine, DiffView, type DiffViewProps, Field, FieldList, type FieldProps, type FieldWidget, type FieldWidgetEditorProps, type FieldWidgetProps, Form, type FormProps, Input, type InputProps, type LayoutRecord, type ListAddress, type ListChange, type ListDisplay, type ListDisplayRow, LogoMark, MenuSelect, type MenuSelectProps, type MetaData, OtpInput, type OtpInputProps, PartView, type PluginInfo, type ProjectUi, type RecordCardProps, RecordLink, ResendCodeButton, type ResendCodeButtonProps, ResponsiveDialog, type ResponsiveDialogProps, Section, Segmented, type SegmentedOption, type SegmentedProps, type ShellLogoProps, SidePanel, type SidePanelProps, SignInPrompt, type SignInPromptProps, Spinner, type Step, StepList, type StepState, Textarea, type TextareaProps, buttonClass, definePlugin, defineUi, diffStats, isConflict, mergeUi, parseDiff, pluginApi, protobaseVersion, uiFromModule, useApiToken, useAppAddress, useAssistant, useAssistantClient, useBackendData, useClient, useFacets, useFieldControl, useHistogram, useLayoutRecord, useList, useListDisplay, useMeta, usePlugin, useRecord, useResourceClient, useSeries, useUpdateRecord };
64
+ export { ActionCard, type ActionCardProps, type ActionContext, type ActionHandler, ActionInputError, type ActionInputProps, ApiProvider, type ApiProviderProps, App, AppLink, type AppProps, AssistantDock, type AssistantDockProps, AuthLayout, type AuthLayoutProps, type BackendData, type BackendRefresh, type BeforeSignInProps, Button, type ButtonProps, type CardAction, type CardTone, ChatMessage, ChatThread, type ChatThreadProps, type ChoiceOption, Choices, type ChoicesProps, CodeForm, type CodeFormProps, CompactTable, type CompactTableProps, Composer, type ComposerProps, type CurrentAddress, Dialog, type DialogProps, type DiffLine, DiffView, type DiffViewProps, Field, FieldList, type FieldProps, type FieldWidget, type FieldWidgetEditorProps, type FieldWidgetProps, Form, type FormProps, Input, type InputProps, type LayoutRecord, type ListAddress, type ListChange, type ListDisplay, type ListDisplayRow, LogoMark, MenuSelect, type MenuSelectProps, type MetaData, OtpInput, type OtpInputProps, PartView, type PluginInfo, type ProjectUi, type RecordCardProps, RecordLink, RecordPicker, type RecordPickerProps, ResendCodeButton, type ResendCodeButtonProps, ResponsiveDialog, type ResponsiveDialogProps, Section, Segmented, type SegmentedOption, type SegmentedProps, type ShellLogoProps, SidePanel, type SidePanelProps, SignInPrompt, type SignInPromptProps, Spinner, type Step, StepList, type StepState, Textarea, type TextareaProps, buttonClass, definePlugin, defineUi, diffStats, isConflict, mergeUi, parseDiff, pluginApi, protobaseVersion, uiFromModule, useApiToken, useAppAddress, useAssistant, useAssistantClient, useBackendData, useClient, useFacets, useFieldControl, useHistogram, useLayoutRecord, useList, useListDisplay, useMeta, usePlugin, useRecord, useResourceClient, useSeries, useUpdateRecord };
package/dist/index.js CHANGED
@@ -50,4 +50,5 @@ import { isConflict, useUpdateRecord } from "./data/use-update-record.js";
50
50
  import { App } from "./app/app.js";
51
51
  import { RecordLink } from "./app/displays/record-link.js";
52
52
  import { Choices } from "./primitives/choices.js";
53
- export { ActionCard, ActionInputError, ApiProvider, App, AppLink, AssistantDock, AuthLayout, Button, ChatMessage, ChatThread, Choices, CodeForm, CompactTable, Composer, Dialog, DiffView, Field, FieldList, Form, Input, LogoMark, MenuSelect, OtpInput, PartView, RecordLink, ResendCodeButton, ResponsiveDialog, Section, Segmented, SidePanel, SignInPrompt, Spinner, StepList, Textarea, buttonClass, definePlugin, defineUi, diffStats, isConflict, mergeUi, parseDiff, pluginApi, protobaseVersion, uiFromModule, useApiToken, useAppAddress, useAssistant, useAssistantClient, useBackendData, useClient, useFacets, useFieldControl, useHistogram, useLayoutRecord, useList, useListDisplay, useMeta, usePlugin, useRecord, useResourceClient, useSeries, useUpdateRecord };
53
+ import { RecordPicker } from "./app/record-picker.js";
54
+ export { ActionCard, ActionInputError, ApiProvider, App, AppLink, AssistantDock, AuthLayout, Button, ChatMessage, ChatThread, Choices, CodeForm, CompactTable, Composer, Dialog, DiffView, Field, FieldList, Form, Input, LogoMark, MenuSelect, OtpInput, PartView, RecordLink, RecordPicker, ResendCodeButton, ResponsiveDialog, Section, Segmented, SidePanel, SignInPrompt, Spinner, StepList, Textarea, buttonClass, definePlugin, defineUi, diffStats, isConflict, mergeUi, parseDiff, pluginApi, protobaseVersion, uiFromModule, useApiToken, useAppAddress, useAssistant, useAssistantClient, useBackendData, useClient, useFacets, useFieldControl, useHistogram, useLayoutRecord, useList, useListDisplay, useMeta, usePlugin, useRecord, useResourceClient, useSeries, useUpdateRecord };
package/dist/package.js CHANGED
@@ -1,5 +1,5 @@
1
1
  //#region package.json
2
- var version = "0.23.2";
2
+ var version = "0.23.4";
3
3
  var protobase = { "pluginApi": 1 };
4
4
  //#endregion
5
5
  export { protobase, version };
@@ -0,0 +1,20 @@
1
+ import { X } from "lucide-react";
2
+ import { jsx, jsxs } from "react/jsx-runtime";
3
+ //#region src/primitives/token.tsx
4
+ /** A chosen value as a small token with a button that takes it off; the button keeps the focus where it is. */
5
+ const Token = ({ label, onRemove }) => /* @__PURE__ */ jsxs("span", {
6
+ className: "inline-flex h-5 max-w-full items-center gap-0.5 rounded bg-primary-soft pl-1.5 pr-0.5 text-xs font-medium text-primary-text",
7
+ children: [/* @__PURE__ */ jsx("span", {
8
+ className: "truncate",
9
+ children: label
10
+ }), /* @__PURE__ */ jsx("button", {
11
+ type: "button",
12
+ "aria-label": `Remove ${label}`,
13
+ className: "rounded p-0.5 hover:bg-primary-border/60",
14
+ onMouseDown: (event) => event.preventDefault(),
15
+ onClick: onRemove,
16
+ children: /* @__PURE__ */ jsx(X, { className: "size-3" })
17
+ })]
18
+ });
19
+ //#endregion
20
+ export { Token };