@panneau/fields 4.1.1 → 5.0.0-alpha.1
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/README.md +74 -0
- package/dist/boolean/BooleanField.d.ts +11 -0
- package/dist/boolean/BooleanField.d.ts.map +1 -0
- package/dist/boolean/BooleanField.js +67 -0
- package/dist/boolean/BooleanField.js.map +1 -0
- package/dist/boolean/index.d.ts +2 -0
- package/dist/boolean/index.d.ts.map +1 -0
- package/dist/boolean/index.js +1 -0
- package/dist/color/ColorField.css +33 -0
- package/dist/color/ColorField.d.ts +16 -0
- package/dist/color/ColorField.d.ts.map +1 -0
- package/dist/color/ColorField.js +187 -0
- package/dist/color/ColorField.js.map +1 -0
- package/dist/color/ColorField.module.js +14 -0
- package/dist/color/ColorField.module.js.map +1 -0
- package/dist/color/index.d.ts +2 -0
- package/dist/color/index.d.ts.map +1 -0
- package/dist/color/index.js +1 -0
- package/dist/date/DateField.d.ts +16 -0
- package/dist/date/DateField.d.ts.map +1 -0
- package/dist/date/DateField.js +132 -0
- package/dist/date/DateField.js.map +1 -0
- package/dist/date/index.d.ts +2 -0
- package/dist/date/index.d.ts.map +1 -0
- package/dist/date/index.js +1 -0
- package/dist/datetime/DateTimeField.d.ts +22 -0
- package/dist/datetime/DateTimeField.d.ts.map +1 -0
- package/dist/datetime/DateTimeField.js +78 -0
- package/dist/datetime/DateTimeField.js.map +1 -0
- package/dist/datetime/index.d.ts +2 -0
- package/dist/datetime/index.d.ts.map +1 -0
- package/dist/datetime/index.js +1 -0
- package/dist/email/EmailField.d.ts +8 -0
- package/dist/email/EmailField.d.ts.map +1 -0
- package/dist/email/EmailField.js +84 -0
- package/dist/email/EmailField.js.map +1 -0
- package/dist/email/index.d.ts +2 -0
- package/dist/email/index.d.ts.map +1 -0
- package/dist/email/index.js +1 -0
- package/dist/group/GroupField.css +14 -0
- package/dist/group/GroupField.d.ts +19 -0
- package/dist/group/GroupField.d.ts.map +1 -0
- package/dist/group/GroupField.js +140 -0
- package/dist/group/GroupField.js.map +1 -0
- package/dist/group/GroupField.module.js +8 -0
- package/dist/group/GroupField.module.js.map +1 -0
- package/dist/group/index.d.ts +2 -0
- package/dist/group/index.d.ts.map +1 -0
- package/dist/group/index.js +1 -0
- package/dist/index.d.ts +8 -12
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +7 -134
- package/dist/locales/index.d.ts +15 -0
- package/dist/locales/index.d.ts.map +1 -0
- package/dist/locales/index.js +21 -0
- package/dist/locales/index.js.map +1 -0
- package/dist/localized/LocalizedField.css +9 -0
- package/dist/localized/LocalizedField.d.ts +36 -0
- package/dist/localized/LocalizedField.d.ts.map +1 -0
- package/dist/localized/LocalizedField.js +207 -0
- package/dist/localized/LocalizedField.js.map +1 -0
- package/dist/localized/LocalizedField.module.js +12 -0
- package/dist/localized/LocalizedField.module.js.map +1 -0
- package/dist/localized/index.d.ts +2 -0
- package/dist/localized/index.d.ts.map +1 -0
- package/dist/localized/index.js +1 -0
- package/dist/missing/MissingField.css +18 -0
- package/dist/missing/MissingField.d.ts +7 -0
- package/dist/missing/MissingField.d.ts.map +1 -0
- package/dist/missing/MissingField.js +68 -0
- package/dist/missing/MissingField.js.map +1 -0
- package/dist/missing/MissingField.module.js +14 -0
- package/dist/missing/MissingField.module.js.map +1 -0
- package/dist/number/NumberField.d.ts +23 -0
- package/dist/number/NumberField.d.ts.map +1 -0
- package/dist/number/NumberField.js +175 -0
- package/dist/number/NumberField.js.map +1 -0
- package/dist/number/index.d.ts +2 -0
- package/dist/number/index.d.ts.map +1 -0
- package/dist/number/index.js +1 -0
- package/dist/relation/RelationField.css +43 -0
- package/dist/relation/RelationField.d.ts +48 -0
- package/dist/relation/RelationField.d.ts.map +1 -0
- package/dist/relation/RelationField.js +328 -0
- package/dist/relation/RelationField.js.map +1 -0
- package/dist/relation/RelationField.module.js +20 -0
- package/dist/relation/RelationField.module.js.map +1 -0
- package/dist/relation/index.d.ts +3 -0
- package/dist/relation/index.d.ts.map +1 -0
- package/dist/relation/index.js +3 -0
- package/dist/repeater/RepeaterField.css +80 -0
- package/dist/repeater/RepeaterField.d.ts +29 -0
- package/dist/repeater/RepeaterField.d.ts.map +1 -0
- package/dist/repeater/RepeaterField.js +220 -0
- package/dist/repeater/RepeaterField.js.map +1 -0
- package/dist/repeater/RepeaterField.module.js +26 -0
- package/dist/repeater/RepeaterField.module.js.map +1 -0
- package/dist/repeater/index.d.ts +2 -0
- package/dist/repeater/index.d.ts.map +1 -0
- package/dist/repeater/index.js +1 -0
- package/dist/select/SelectField.css +5 -0
- package/dist/select/SelectField.d.ts +38 -0
- package/dist/select/SelectField.d.ts.map +1 -0
- package/dist/select/SelectField.js +434 -0
- package/dist/select/SelectField.js.map +1 -0
- package/dist/select/SelectField.module.js +8 -0
- package/dist/select/SelectField.module.js.map +1 -0
- package/dist/select/index.d.ts +2 -0
- package/dist/select/index.d.ts.map +1 -0
- package/dist/select/index.js +1 -0
- package/dist/shared/DefinitionField.d.ts +32 -0
- package/dist/shared/DefinitionField.d.ts.map +1 -0
- package/dist/shared/DefinitionField.js +153 -0
- package/dist/shared/DefinitionField.js.map +1 -0
- package/dist/shared/builtIn.d.ts +17 -0
- package/dist/shared/builtIn.d.ts.map +1 -0
- package/dist/shared/builtIn.js +52 -0
- package/dist/shared/builtIn.js.map +1 -0
- package/dist/shared/context.d.ts +55 -0
- package/dist/shared/context.d.ts.map +1 -0
- package/dist/shared/context.js +72 -0
- package/dist/shared/context.js.map +1 -0
- package/dist/shared/cx.d.ts +3 -0
- package/dist/shared/cx.d.ts.map +1 -0
- package/dist/shared/cx.js +9 -0
- package/dist/shared/cx.js.map +1 -0
- package/dist/shared/dates.d.ts +15 -0
- package/dist/shared/dates.d.ts.map +1 -0
- package/dist/shared/dates.js +49 -0
- package/dist/shared/dates.js.map +1 -0
- package/dist/shared/entry.d.ts +23 -0
- package/dist/shared/entry.d.ts.map +1 -0
- package/dist/shared/entry.js +45 -0
- package/dist/shared/entry.js.map +1 -0
- package/dist/shared/messages.d.ts +2 -0
- package/dist/shared/messages.d.ts.map +1 -0
- package/dist/shared/messages.js +1 -0
- package/dist/shared/relation.d.ts +73 -0
- package/dist/shared/relation.d.ts.map +1 -0
- package/dist/shared/relation.js +156 -0
- package/dist/shared/relation.js.map +1 -0
- package/dist/shared/types.d.ts +50 -0
- package/dist/shared/types.d.ts.map +1 -0
- package/dist/slug/SlugField.d.ts +24 -0
- package/dist/slug/SlugField.d.ts.map +1 -0
- package/dist/slug/SlugField.js +216 -0
- package/dist/slug/SlugField.js.map +1 -0
- package/dist/slug/index.d.ts +2 -0
- package/dist/slug/index.d.ts.map +1 -0
- package/dist/slug/index.js +1 -0
- package/dist/text/TextField.d.ts +24 -0
- package/dist/text/TextField.d.ts.map +1 -0
- package/dist/text/TextField.js +179 -0
- package/dist/text/TextField.js.map +1 -0
- package/dist/text/index.d.ts +2 -0
- package/dist/text/index.d.ts.map +1 -0
- package/dist/text/index.js +1 -0
- package/dist/textarea/TextAreaField.d.ts +14 -0
- package/dist/textarea/TextAreaField.d.ts.map +1 -0
- package/dist/textarea/TextAreaField.js +126 -0
- package/dist/textarea/TextAreaField.js.map +1 -0
- package/dist/textarea/index.d.ts +2 -0
- package/dist/textarea/index.d.ts.map +1 -0
- package/dist/textarea/index.js +1 -0
- package/dist/time/TimeField.d.ts +13 -0
- package/dist/time/TimeField.d.ts.map +1 -0
- package/dist/time/TimeField.js +49 -0
- package/dist/time/TimeField.js.map +1 -0
- package/dist/time/index.d.ts +2 -0
- package/dist/time/index.d.ts.map +1 -0
- package/dist/time/index.js +1 -0
- package/dist/url/UrlField.css +11 -0
- package/dist/url/UrlField.d.ts +16 -0
- package/dist/url/UrlField.d.ts.map +1 -0
- package/dist/url/UrlField.js +94 -0
- package/dist/url/UrlField.js.map +1 -0
- package/dist/url/UrlField.module.js +8 -0
- package/dist/url/UrlField.module.js.map +1 -0
- package/dist/url/index.d.ts +2 -0
- package/dist/url/index.d.ts.map +1 -0
- package/dist/url/index.js +1 -0
- package/package.json +108 -79
- package/assets/css/styles.css +0 -14
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
import { c } from "react/compiler-runtime";
|
|
2
|
+
import { useEffect, useState } from "react";
|
|
3
|
+
//#region src/shared/relation.ts
|
|
4
|
+
function labelOf(value, locale) {
|
|
5
|
+
if (typeof value === "string" || typeof value === "number") return String(value);
|
|
6
|
+
if (value !== null && typeof value === "object") {
|
|
7
|
+
const localized = value;
|
|
8
|
+
const first = (locale !== void 0 ? localized[locale] : void 0) ?? Object.values(localized)[0];
|
|
9
|
+
return typeof first === "string" ? first : null;
|
|
10
|
+
}
|
|
11
|
+
return null;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* A relation source over an adapter, such as the `DataAdapter` of `@panneau/data`: a search is a
|
|
15
|
+
* list query with `q`, ids are resolved with `get()`, and, with the `create` option, items are
|
|
16
|
+
* created with `create()`. Labels come from the resource's `itemTitle`.
|
|
17
|
+
*/
|
|
18
|
+
function createRelationSource(adapter, { definition = null, perPage = 20, getLabel, create } = {}) {
|
|
19
|
+
const resourceOf = (resource) => definition?.resources.find((candidate) => candidate.id === resource);
|
|
20
|
+
const toReference = (item, resource) => {
|
|
21
|
+
const titleField = resourceOf(resource)?.itemTitle;
|
|
22
|
+
return {
|
|
23
|
+
id: item.id,
|
|
24
|
+
label: getLabel?.(item, resource) ?? (titleField !== void 0 ? labelOf(item[titleField]) : null) ?? item.id
|
|
25
|
+
};
|
|
26
|
+
};
|
|
27
|
+
const valuesOf = (text, resource) => {
|
|
28
|
+
if (typeof create === "function") return create(text, resource);
|
|
29
|
+
const definitionResource = resourceOf(resource);
|
|
30
|
+
const titleField = definitionResource?.itemTitle;
|
|
31
|
+
if (definitionResource === void 0 || titleField === void 0) return null;
|
|
32
|
+
const field = definitionResource.fields.find((candidate) => candidate.name === titleField);
|
|
33
|
+
const locales = definition?.locales ?? [];
|
|
34
|
+
return { [titleField]: field?.localized === true && locales.length > 0 ? Object.fromEntries(locales.map((locale) => [locale, text])) : text };
|
|
35
|
+
};
|
|
36
|
+
const source = { async search(resource, query, { signal }) {
|
|
37
|
+
return (await adapter.list(resource, {
|
|
38
|
+
page: 1,
|
|
39
|
+
perPage,
|
|
40
|
+
...query !== "" ? { q: query } : {}
|
|
41
|
+
}, { signal })).data.map((item) => toReference(item, resource));
|
|
42
|
+
} };
|
|
43
|
+
const { get, create: createItem } = adapter;
|
|
44
|
+
if (get !== void 0) source.resolve = async (resource, ids, { signal }) => {
|
|
45
|
+
return (await Promise.allSettled(ids.map((id) => get.call(adapter, resource, id, { signal })))).flatMap((result) => result.status === "fulfilled" ? [toReference(result.value, resource)] : []);
|
|
46
|
+
};
|
|
47
|
+
if (create !== void 0 && create !== false && createItem !== void 0) {
|
|
48
|
+
source.canCreate = (resource) => resourceOf(resource)?.abilities?.create !== false && valuesOf("", resource) !== null;
|
|
49
|
+
source.create = async (resource, text) => {
|
|
50
|
+
const values = valuesOf(text, resource);
|
|
51
|
+
if (values === null) return null;
|
|
52
|
+
return toReference(await createItem.call(adapter, resource, values), resource);
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
return source;
|
|
56
|
+
}
|
|
57
|
+
/** Reads relation values: references, ids, or lists of them. */
|
|
58
|
+
function toReferences(value) {
|
|
59
|
+
return (Array.isArray(value) ? value : value === null || value === void 0 ? [] : [value]).flatMap((entry) => {
|
|
60
|
+
if (typeof entry === "string" || typeof entry === "number") return [{
|
|
61
|
+
id: String(entry),
|
|
62
|
+
label: String(entry)
|
|
63
|
+
}];
|
|
64
|
+
if (entry !== null && typeof entry === "object" && "id" in entry) {
|
|
65
|
+
const { id, label } = entry;
|
|
66
|
+
if (typeof id === "string" || typeof id === "number") return [{
|
|
67
|
+
id: String(id),
|
|
68
|
+
label: typeof label === "string" ? label : String(id)
|
|
69
|
+
}];
|
|
70
|
+
}
|
|
71
|
+
return [];
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
var resolved = /* @__PURE__ */ new WeakMap();
|
|
75
|
+
function resolvedOf(source) {
|
|
76
|
+
let entry = resolved.get(source);
|
|
77
|
+
if (entry === void 0) {
|
|
78
|
+
entry = {
|
|
79
|
+
labels: /* @__PURE__ */ new Map(),
|
|
80
|
+
pending: /* @__PURE__ */ new Map()
|
|
81
|
+
};
|
|
82
|
+
resolved.set(source, entry);
|
|
83
|
+
}
|
|
84
|
+
return entry;
|
|
85
|
+
}
|
|
86
|
+
var keyOf = (resource, id) => `${resource}\u0000${id}`;
|
|
87
|
+
/** Resolves the ids that no request has resolved or is resolving yet, and waits for all of them. */
|
|
88
|
+
function resolveIds(source, resource, ids) {
|
|
89
|
+
const { labels, pending } = resolvedOf(source);
|
|
90
|
+
const fresh = ids.filter((id) => !pending.has(keyOf(resource, id)));
|
|
91
|
+
if (fresh.length > 0 && source.resolve !== void 0) {
|
|
92
|
+
const request = source.resolve(resource, fresh, { signal: new AbortController().signal }).then((found) => {
|
|
93
|
+
fresh.forEach((id) => labels.set(keyOf(resource, id), found.find((candidate) => candidate.id === id)?.label ?? null));
|
|
94
|
+
}, () => {
|
|
95
|
+
fresh.forEach((id) => pending.delete(keyOf(resource, id)));
|
|
96
|
+
});
|
|
97
|
+
fresh.forEach((id) => pending.set(keyOf(resource, id), request));
|
|
98
|
+
}
|
|
99
|
+
return Promise.all(ids.map((id) => pending.get(keyOf(resource, id))));
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Relation references with the labels of plain ids: references whose label is their id (from
|
|
103
|
+
* `"12"`) are resolved with the source's `resolve()`, once per source, resource and id. They keep
|
|
104
|
+
* their id as label until then, or when the source doesn't find them.
|
|
105
|
+
*/
|
|
106
|
+
function useRelationReferences(source, resource, references) {
|
|
107
|
+
const $ = c(1);
|
|
108
|
+
const cache = source !== null ? resolvedOf(source).labels : null;
|
|
109
|
+
let t0;
|
|
110
|
+
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
|
|
111
|
+
t0 = {};
|
|
112
|
+
$[0] = t0;
|
|
113
|
+
} else t0 = $[0];
|
|
114
|
+
const [labels, setLabels] = useState(t0);
|
|
115
|
+
const label = (id) => labels[keyOf(resource, id)] ?? cache?.get(keyOf(resource, id)) ?? void 0;
|
|
116
|
+
const missingKey = (source?.resolve !== void 0 && cache !== null ? references.filter(_temp).map(_temp2).filter((id_0) => !cache.has(keyOf(resource, id_0))) : []).join("\0");
|
|
117
|
+
useEffect(() => {
|
|
118
|
+
const ids = missingKey === "" ? [] : missingKey.split("\0");
|
|
119
|
+
if (source === null || cache === null || ids.length === 0) return;
|
|
120
|
+
let active = true;
|
|
121
|
+
resolveIds(source, resource, ids).then(() => {
|
|
122
|
+
if (!active) return;
|
|
123
|
+
const next = {};
|
|
124
|
+
ids.forEach((id_1) => {
|
|
125
|
+
const found = cache.get(keyOf(resource, id_1));
|
|
126
|
+
if (typeof found === "string") next[keyOf(resource, id_1)] = found;
|
|
127
|
+
});
|
|
128
|
+
setLabels((current) => ({
|
|
129
|
+
...current,
|
|
130
|
+
...next
|
|
131
|
+
}));
|
|
132
|
+
});
|
|
133
|
+
return () => {
|
|
134
|
+
active = false;
|
|
135
|
+
};
|
|
136
|
+
}, [
|
|
137
|
+
source,
|
|
138
|
+
cache,
|
|
139
|
+
resource,
|
|
140
|
+
missingKey
|
|
141
|
+
]);
|
|
142
|
+
return references.map((reference_1) => reference_1.label === reference_1.id ? {
|
|
143
|
+
id: reference_1.id,
|
|
144
|
+
label: label(reference_1.id) ?? reference_1.id
|
|
145
|
+
} : reference_1);
|
|
146
|
+
}
|
|
147
|
+
function _temp2(reference_0) {
|
|
148
|
+
return reference_0.id;
|
|
149
|
+
}
|
|
150
|
+
function _temp(reference) {
|
|
151
|
+
return reference.label === reference.id;
|
|
152
|
+
}
|
|
153
|
+
//#endregion
|
|
154
|
+
export { createRelationSource, toReferences, useRelationReferences };
|
|
155
|
+
|
|
156
|
+
//# sourceMappingURL=relation.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"relation.js","names":["id","active","reference","_c"],"sources":["../../src/shared/relation.ts"],"sourcesContent":["import { useEffect, useState } from 'react';\n\nimport type { Definition, Item, ListQuery } from '@panneau/protocol';\n\n/** A related item: its id, and a label to show it. The value of a relation field. */\nexport interface RelationReference {\n id: string;\n label: string;\n}\n\n/** Where relation fields search for items. */\nexport interface RelationSource {\n /** Items of a resource that match a text, for the picker. */\n search(\n resource: string,\n query: string,\n options: { signal: AbortSignal },\n ): Promise<RelationReference[]>;\n /**\n * The references of items by id, for values that are only ids (`\"12\"`, `[\"12\", \"13\"]`), as a\n * product's own API gives them. In any order; ids it doesn't find are left out, and keep their\n * id as label. Without it, fields show the ids.\n */\n resolve?(\n resource: string,\n ids: readonly string[],\n options: { signal: AbortSignal },\n ): Promise<RelationReference[]>;\n /**\n * Creates an item from the text typed in the picker, which offers \"Create “…”\" when this\n * method exists. It can open a dialog, and resolves with the reference of the new item, or with\n * `null` when nothing was created.\n */\n create?(resource: string, text: string): Promise<RelationReference | null>;\n /** Whether `create()` can create items of a resource. Defaults to true. */\n canCreate?(resource: string): boolean;\n}\n\n/** What `createRelationSource()` needs from an adapter: the `list()`, `get()` and `create()` of `@panneau/data`. */\nexport interface RelationListAdapter {\n list(\n resource: string,\n query?: ListQuery,\n options?: { signal?: AbortSignal },\n ): Promise<{ data: Item[] }>;\n /** One item, for `resolve()`. Without it, the source doesn't resolve ids. */\n get?(resource: string, id: string, options?: { signal?: AbortSignal }): Promise<Item>;\n /** Creates an item, for `create()`. */\n create?(resource: string, values: Record<string, unknown>): Promise<Item>;\n}\n\nexport interface RelationSourceOptions {\n /** The definition, for the field that titles the items of each resource (`itemTitle`). */\n definition?: (Pick<Definition, 'resources'> & Partial<Pick<Definition, 'locales'>>) | null;\n /** Items per search. Defaults to 20. */\n perPage?: number;\n /** The label of an item, when `itemTitle` isn't enough. */\n getLabel?: (item: Item, resource: string) => string;\n /**\n * Lets the picker create items, with the adapter's `create()`. `true` sets the typed text as the\n * value of the resource's `itemTitle` field (in every locale of the definition when it is\n * localized), on the resources that allow `create`. A function gives the values of the new item\n * instead, or `null` for a resource where items can't be created.\n */\n create?: boolean | ((text: string, resource: string) => Record<string, unknown> | null);\n}\n\nfunction labelOf(value: unknown, locale?: string): string | null {\n if (typeof value === 'string' || typeof value === 'number') {\n return String(value);\n }\n if (value !== null && typeof value === 'object') {\n const localized = value as Record<string, unknown>;\n const first =\n (locale !== undefined ? localized[locale] : undefined) ?? Object.values(localized)[0];\n return typeof first === 'string' ? first : null;\n }\n return null;\n}\n\n/**\n * A relation source over an adapter, such as the `DataAdapter` of `@panneau/data`: a search is a\n * list query with `q`, ids are resolved with `get()`, and, with the `create` option, items are\n * created with `create()`. Labels come from the resource's `itemTitle`.\n */\nexport function createRelationSource(\n adapter: RelationListAdapter,\n { definition = null, perPage = 20, getLabel, create }: RelationSourceOptions = {},\n): RelationSource {\n const resourceOf = (resource: string) =>\n definition?.resources.find((candidate) => candidate.id === resource);\n const toReference = (item: Item, resource: string): RelationReference => {\n const titleField = resourceOf(resource)?.itemTitle;\n return {\n id: item.id,\n label:\n getLabel?.(item, resource) ??\n (titleField !== undefined ? labelOf(item[titleField]) : null) ??\n item.id,\n };\n };\n const valuesOf = (text: string, resource: string): Record<string, unknown> | null => {\n if (typeof create === 'function') {\n return create(text, resource);\n }\n const definitionResource = resourceOf(resource);\n const titleField = definitionResource?.itemTitle;\n if (definitionResource === undefined || titleField === undefined) {\n return null;\n }\n const field = definitionResource.fields.find((candidate) => candidate.name === titleField);\n const locales = definition?.locales ?? [];\n return {\n [titleField]:\n field?.localized === true && locales.length > 0\n ? Object.fromEntries(locales.map((locale) => [locale, text]))\n : text,\n };\n };\n const source: RelationSource = {\n async search(resource, query, { signal }) {\n const response = await adapter.list(\n resource,\n { page: 1, perPage, ...(query !== '' ? { q: query } : {}) },\n { signal },\n );\n return response.data.map((item) => toReference(item, resource));\n },\n };\n const { get, create: createItem } = adapter;\n if (get !== undefined) {\n source.resolve = async (resource, ids, { signal }) => {\n const results = await Promise.allSettled(\n ids.map((id) => get.call(adapter, resource, id, { signal })),\n );\n return results.flatMap((result) =>\n result.status === 'fulfilled' ? [toReference(result.value, resource)] : [],\n );\n };\n }\n if (create !== undefined && create !== false && createItem !== undefined) {\n source.canCreate = (resource) =>\n resourceOf(resource)?.abilities?.create !== false && valuesOf('', resource) !== null;\n source.create = async (resource, text) => {\n const values = valuesOf(text, resource);\n if (values === null) {\n return null;\n }\n return toReference(await createItem.call(adapter, resource, values), resource);\n };\n }\n return source;\n}\n\n/** Reads relation values: references, ids, or lists of them. */\nexport function toReferences(value: unknown): RelationReference[] {\n const list = Array.isArray(value)\n ? value\n : value === null || value === undefined\n ? []\n : [value];\n return list.flatMap((entry: unknown) => {\n if (typeof entry === 'string' || typeof entry === 'number') {\n return [{ id: String(entry), label: String(entry) }];\n }\n if (entry !== null && typeof entry === 'object' && 'id' in entry) {\n const { id, label } = entry as { id: unknown; label?: unknown };\n if (typeof id === 'string' || typeof id === 'number') {\n return [{ id: String(id), label: typeof label === 'string' ? label : String(id) }];\n }\n }\n return [];\n });\n}\n\ninterface ResolvedLabels {\n /** Labels by resource and id: `null` for ids that the source didn't find. */\n labels: Map<string, string | null>;\n /** Requests in flight, so that fields with the same ids share one. */\n pending: Map<string, Promise<void>>;\n}\n\nconst resolved = new WeakMap<RelationSource, ResolvedLabels>();\n\nfunction resolvedOf(source: RelationSource): ResolvedLabels {\n let entry = resolved.get(source);\n if (entry === undefined) {\n entry = { labels: new Map(), pending: new Map() };\n resolved.set(source, entry);\n }\n return entry;\n}\n\nconst keyOf = (resource: string, id: string) => `${resource}\\u0000${id}`;\n\n/** Resolves the ids that no request has resolved or is resolving yet, and waits for all of them. */\nfunction resolveIds(source: RelationSource, resource: string, ids: readonly string[]) {\n const { labels, pending } = resolvedOf(source);\n const fresh = ids.filter((id) => !pending.has(keyOf(resource, id)));\n if (fresh.length > 0 && source.resolve !== undefined) {\n // Shared by every field that asks for these ids: it isn't aborted when one unmounts.\n const request = source\n .resolve(resource, fresh, { signal: new AbortController().signal })\n .then(\n (found) => {\n fresh.forEach((id) =>\n labels.set(\n keyOf(resource, id),\n found.find((candidate) => candidate.id === id)?.label ?? null,\n ),\n );\n },\n () => {\n // Tried again the next time a field shows these ids.\n fresh.forEach((id) => pending.delete(keyOf(resource, id)));\n },\n );\n fresh.forEach((id) => pending.set(keyOf(resource, id), request));\n }\n return Promise.all(ids.map((id) => pending.get(keyOf(resource, id))));\n}\n\n/**\n * Relation references with the labels of plain ids: references whose label is their id (from\n * `\"12\"`) are resolved with the source's `resolve()`, once per source, resource and id. They keep\n * their id as label until then, or when the source doesn't find them.\n */\nexport function useRelationReferences(\n source: RelationSource | null,\n resource: string,\n references: readonly RelationReference[],\n): RelationReference[] {\n const cache = source !== null ? resolvedOf(source).labels : null;\n const [labels, setLabels] = useState<Readonly<Record<string, string>>>({});\n const label = (id: string): string | undefined =>\n labels[keyOf(resource, id)] ?? cache?.get(keyOf(resource, id)) ?? undefined;\n const missing =\n source?.resolve !== undefined && cache !== null\n ? references\n .filter((reference) => reference.label === reference.id)\n .map((reference) => reference.id)\n .filter((id) => !cache.has(keyOf(resource, id)))\n : [];\n const missingKey = missing.join('\\u0000');\n\n useEffect(() => {\n const ids = missingKey === '' ? [] : missingKey.split('\\u0000');\n if (source === null || cache === null || ids.length === 0) {\n return undefined;\n }\n let active = true;\n void resolveIds(source, resource, ids).then(() => {\n if (!active) {\n return;\n }\n const next: Record<string, string> = {};\n ids.forEach((id) => {\n const found = cache.get(keyOf(resource, id));\n if (typeof found === 'string') {\n next[keyOf(resource, id)] = found;\n }\n });\n setLabels((current) => ({ ...current, ...next }));\n });\n return () => {\n active = false;\n };\n }, [source, cache, resource, missingKey]);\n\n return references.map((reference) =>\n reference.label === reference.id\n ? { id: reference.id, label: label(reference.id) ?? reference.id }\n : reference,\n );\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import type { ComponentType, ReactNode } from 'react';
|
|
2
|
+
import type { Field } from '@panneau/protocol';
|
|
3
|
+
/**
|
|
4
|
+
* Props of every field component. Fields are controlled: they show `value` and report changes
|
|
5
|
+
* through `onChange`, and keep no value of their own. An empty value is `null`.
|
|
6
|
+
*/
|
|
7
|
+
export interface FieldProps<V> {
|
|
8
|
+
value?: V | null;
|
|
9
|
+
onChange?: (value: V | null) => void;
|
|
10
|
+
/** Called when the field loses focus: forms show its client errors from then on. */
|
|
11
|
+
onBlur?: () => void;
|
|
12
|
+
/** Error messages. Any message makes the field invalid. */
|
|
13
|
+
errors?: readonly string[];
|
|
14
|
+
disabled?: boolean;
|
|
15
|
+
readOnly?: boolean;
|
|
16
|
+
/** Marks the field as required. Validation itself is the form's job. */
|
|
17
|
+
required?: boolean;
|
|
18
|
+
label?: ReactNode;
|
|
19
|
+
/** Help text under the field. */
|
|
20
|
+
description?: ReactNode;
|
|
21
|
+
/** Name of the field, also used as the name of its input. */
|
|
22
|
+
name?: string;
|
|
23
|
+
id?: string;
|
|
24
|
+
autoFocus?: boolean;
|
|
25
|
+
/** Accessible label, for a field without a visible label. */
|
|
26
|
+
'aria-label'?: string;
|
|
27
|
+
className?: string;
|
|
28
|
+
labelClassName?: string;
|
|
29
|
+
descriptionClassName?: string;
|
|
30
|
+
errorsClassName?: string;
|
|
31
|
+
}
|
|
32
|
+
/** Props of a registry entry: a field component driven by a field of the definition. */
|
|
33
|
+
export interface FieldEntryProps {
|
|
34
|
+
/** The field, as the definition describes it. */
|
|
35
|
+
field: Field;
|
|
36
|
+
/** Dot path of the value (`blocks.2.title`), unique in the form. */
|
|
37
|
+
path: string;
|
|
38
|
+
value: unknown;
|
|
39
|
+
onChange: (value: unknown) => void;
|
|
40
|
+
onBlur?: () => void;
|
|
41
|
+
errors?: readonly string[];
|
|
42
|
+
disabled?: boolean;
|
|
43
|
+
readOnly?: boolean;
|
|
44
|
+
/** The object the field belongs to: the form's values, or a group or a block. */
|
|
45
|
+
parentValue?: unknown;
|
|
46
|
+
className?: string;
|
|
47
|
+
}
|
|
48
|
+
/** A registry entry: renders the field of a type from its definition. */
|
|
49
|
+
export type FieldEntry = ComponentType<FieldEntryProps>;
|
|
50
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/shared/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAEtD,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,mBAAmB,CAAC;AAE/C;;;GAGG;AACH,MAAM,WAAW,UAAU,CAAC,CAAC;IACzB,KAAK,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC;IACjB,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,CAAC,GAAG,IAAI,KAAK,IAAI,CAAC;IACrC,oFAAoF;IACpF,MAAM,CAAC,EAAE,MAAM,IAAI,CAAC;IACpB,2DAA2D;IAC3D,MAAM,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAC3B,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,wEAAwE;IACxE,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,KAAK,CAAC,EAAE,SAAS,CAAC;IAClB,iCAAiC;IACjC,WAAW,CAAC,EAAE,SAAS,CAAC;IACxB,6DAA6D;IAC7D,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,6DAA6D;IAC7D,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B,eAAe,CAAC,EAAE,MAAM,CAAC;CAC5B;AAED,wFAAwF;AACxF,MAAM,WAAW,eAAe;IAC5B,iDAAiD;IACjD,KAAK,EAAE,KAAK,CAAC;IACb,oEAAoE;IACpE,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,OAAO,CAAC;IACf,QAAQ,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,CAAC;IACnC,MAAM,CAAC,EAAE,MAAM,IAAI,CAAC;IACpB,MAAM,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAC3B,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,iFAAiF;IACjF,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,SAAS,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,yEAAyE;AACzE,MAAM,MAAM,UAAU,GAAG,aAAa,CAAC,eAAe,CAAC,CAAC"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { FieldEntryProps } from '../shared/types';
|
|
2
|
+
import { type TextFieldProps } from '../text/TextField';
|
|
3
|
+
/**
|
|
4
|
+
* Turns a text into a slug: lowercase ASCII letters and digits separated by dashes, without
|
|
5
|
+
* accents (`Été à Montréal` gives `ete-a-montreal`).
|
|
6
|
+
*/
|
|
7
|
+
export declare function slugify(text: string): string;
|
|
8
|
+
export interface SlugFieldProps extends Omit<TextFieldProps, 'type' | 'suffix'> {
|
|
9
|
+
/**
|
|
10
|
+
* The text the slug is made from (a title). While the slug is empty or still matches it, the
|
|
11
|
+
* slug follows it.
|
|
12
|
+
*/
|
|
13
|
+
source?: string | null;
|
|
14
|
+
/** Name of the source, for the button that generates the slug from it. */
|
|
15
|
+
sourceLabel?: string;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* A slug for URLs. Typed text is cleaned up on blur. With a `source`, the slug follows it until
|
|
19
|
+
* it is edited, and a button generates it again.
|
|
20
|
+
*/
|
|
21
|
+
export declare function SlugField({ value, onChange, onBlur, source, sourceLabel, disabled, readOnly, ...props }: SlugFieldProps): import("react").JSX.Element;
|
|
22
|
+
/** The `slug` type: `from` names the field it follows, and setting `prefix` shows a URL prefix. */
|
|
23
|
+
export declare function SlugFieldEntry(props: FieldEntryProps): import("react").JSX.Element;
|
|
24
|
+
//# sourceMappingURL=SlugField.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SlugField.d.ts","sourceRoot":"","sources":["../../src/slug/SlugField.tsx"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AACvD,OAAO,EAAa,KAAK,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAEnE;;;GAGG;AACH,wBAAgB,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAO5C;AAED,MAAM,WAAW,cAAe,SAAQ,IAAI,CAAC,cAAc,EAAE,MAAM,GAAG,QAAQ,CAAC;IAC3E;;;OAGG;IACH,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,0EAA0E;IAC1E,WAAW,CAAC,EAAE,MAAM,CAAC;CACxB;AAED;;;GAGG;AACH,wBAAgB,SAAS,CAAC,EACtB,KAAK,EACL,QAAQ,EACR,MAAM,EACN,MAAM,EACN,WAAW,EACX,QAAQ,EACR,QAAQ,EACR,GAAG,KAAK,EACX,EAAE,cAAc,+BAqDhB;AAcD,mGAAmG;AACnG,wBAAgB,cAAc,CAAC,KAAK,EAAE,eAAe,+BAgBpD"}
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
import { getFieldProps, getSetting, isString } from "../shared/entry.js";
|
|
2
|
+
import { messages } from "../shared/messages.js";
|
|
3
|
+
import { TextField } from "../text/TextField.js";
|
|
4
|
+
import { c } from "react/compiler-runtime";
|
|
5
|
+
import { IconButton, usePanneauIntl } from "@panneau/ui";
|
|
6
|
+
import { useEffect, useRef } from "react";
|
|
7
|
+
import { jsx } from "react/jsx-runtime";
|
|
8
|
+
import { WandSparkles } from "lucide-react";
|
|
9
|
+
//#region src/slug/SlugField.tsx
|
|
10
|
+
/**
|
|
11
|
+
* Turns a text into a slug: lowercase ASCII letters and digits separated by dashes, without
|
|
12
|
+
* accents (`Été à Montréal` gives `ete-a-montreal`).
|
|
13
|
+
*/
|
|
14
|
+
function slugify(text) {
|
|
15
|
+
return text.normalize("NFKD").replace(/[̀-ͯ]/g, "").toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* A slug for URLs. Typed text is cleaned up on blur. With a `source`, the slug follows it until
|
|
19
|
+
* it is edited, and a button generates it again.
|
|
20
|
+
*/
|
|
21
|
+
function SlugField(t0) {
|
|
22
|
+
const $ = c(37);
|
|
23
|
+
let disabled;
|
|
24
|
+
let onBlur;
|
|
25
|
+
let onChange;
|
|
26
|
+
let props;
|
|
27
|
+
let readOnly;
|
|
28
|
+
let source;
|
|
29
|
+
let sourceLabel;
|
|
30
|
+
let value;
|
|
31
|
+
if ($[0] !== t0) {
|
|
32
|
+
({value, onChange, onBlur, source, sourceLabel, disabled, readOnly, ...props} = t0);
|
|
33
|
+
$[0] = t0;
|
|
34
|
+
$[1] = disabled;
|
|
35
|
+
$[2] = onBlur;
|
|
36
|
+
$[3] = onChange;
|
|
37
|
+
$[4] = props;
|
|
38
|
+
$[5] = readOnly;
|
|
39
|
+
$[6] = source;
|
|
40
|
+
$[7] = sourceLabel;
|
|
41
|
+
$[8] = value;
|
|
42
|
+
} else {
|
|
43
|
+
disabled = $[1];
|
|
44
|
+
onBlur = $[2];
|
|
45
|
+
onChange = $[3];
|
|
46
|
+
props = $[4];
|
|
47
|
+
readOnly = $[5];
|
|
48
|
+
source = $[6];
|
|
49
|
+
sourceLabel = $[7];
|
|
50
|
+
value = $[8];
|
|
51
|
+
}
|
|
52
|
+
const intl = usePanneauIntl();
|
|
53
|
+
const previousSource = useRef(source);
|
|
54
|
+
const editable = disabled !== true && readOnly !== true;
|
|
55
|
+
let t1;
|
|
56
|
+
let t2;
|
|
57
|
+
if ($[9] !== editable || $[10] !== onChange || $[11] !== source || $[12] !== value) {
|
|
58
|
+
t1 = () => {
|
|
59
|
+
const previous = previousSource.current;
|
|
60
|
+
previousSource.current = source;
|
|
61
|
+
if (!editable || source === previous || source === void 0) return;
|
|
62
|
+
const current = value ?? "";
|
|
63
|
+
if (current === "" || current === slugify(previous ?? "")) {
|
|
64
|
+
const next = slugify(source ?? "");
|
|
65
|
+
if (next !== current) onChange?.(next === "" ? null : next);
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
t2 = [
|
|
69
|
+
source,
|
|
70
|
+
value,
|
|
71
|
+
onChange,
|
|
72
|
+
editable
|
|
73
|
+
];
|
|
74
|
+
$[9] = editable;
|
|
75
|
+
$[10] = onChange;
|
|
76
|
+
$[11] = source;
|
|
77
|
+
$[12] = value;
|
|
78
|
+
$[13] = t1;
|
|
79
|
+
$[14] = t2;
|
|
80
|
+
} else {
|
|
81
|
+
t1 = $[13];
|
|
82
|
+
t2 = $[14];
|
|
83
|
+
}
|
|
84
|
+
useEffect(t1, t2);
|
|
85
|
+
const t3 = source ?? "";
|
|
86
|
+
let t4;
|
|
87
|
+
if ($[15] !== t3) {
|
|
88
|
+
t4 = slugify(t3);
|
|
89
|
+
$[15] = t3;
|
|
90
|
+
$[16] = t4;
|
|
91
|
+
} else t4 = $[16];
|
|
92
|
+
const generated = t4;
|
|
93
|
+
let t5;
|
|
94
|
+
if ($[17] !== onBlur || $[18] !== onChange || $[19] !== value) {
|
|
95
|
+
t5 = () => {
|
|
96
|
+
if (value !== null && value !== void 0) {
|
|
97
|
+
const next_0 = slugify(value);
|
|
98
|
+
if (next_0 !== value) onChange?.(next_0 === "" ? null : next_0);
|
|
99
|
+
}
|
|
100
|
+
onBlur?.();
|
|
101
|
+
};
|
|
102
|
+
$[17] = onBlur;
|
|
103
|
+
$[18] = onChange;
|
|
104
|
+
$[19] = value;
|
|
105
|
+
$[20] = t5;
|
|
106
|
+
} else t5 = $[20];
|
|
107
|
+
let t6;
|
|
108
|
+
if ($[21] !== editable || $[22] !== generated || $[23] !== intl || $[24] !== onChange || $[25] !== source || $[26] !== sourceLabel || $[27] !== value) {
|
|
109
|
+
t6 = source !== void 0 && editable ? /* @__PURE__ */ jsx(IconButton, {
|
|
110
|
+
icon: WandSparkles,
|
|
111
|
+
size: "sm",
|
|
112
|
+
label: intl.formatMessage(messages.generateSlug, { field: sourceLabel ?? "" }),
|
|
113
|
+
isDisabled: generated === "" || generated === value,
|
|
114
|
+
onPress: () => onChange?.(generated === "" ? null : generated)
|
|
115
|
+
}) : void 0;
|
|
116
|
+
$[21] = editable;
|
|
117
|
+
$[22] = generated;
|
|
118
|
+
$[23] = intl;
|
|
119
|
+
$[24] = onChange;
|
|
120
|
+
$[25] = source;
|
|
121
|
+
$[26] = sourceLabel;
|
|
122
|
+
$[27] = value;
|
|
123
|
+
$[28] = t6;
|
|
124
|
+
} else t6 = $[28];
|
|
125
|
+
let t7;
|
|
126
|
+
if ($[29] !== disabled || $[30] !== onChange || $[31] !== props || $[32] !== readOnly || $[33] !== t5 || $[34] !== t6 || $[35] !== value) {
|
|
127
|
+
t7 = /* @__PURE__ */ jsx(TextField, {
|
|
128
|
+
autoComplete: "off",
|
|
129
|
+
...props,
|
|
130
|
+
value,
|
|
131
|
+
onChange,
|
|
132
|
+
disabled,
|
|
133
|
+
readOnly,
|
|
134
|
+
onBlur: t5,
|
|
135
|
+
suffix: t6
|
|
136
|
+
});
|
|
137
|
+
$[29] = disabled;
|
|
138
|
+
$[30] = onChange;
|
|
139
|
+
$[31] = props;
|
|
140
|
+
$[32] = readOnly;
|
|
141
|
+
$[33] = t5;
|
|
142
|
+
$[34] = t6;
|
|
143
|
+
$[35] = value;
|
|
144
|
+
$[36] = t7;
|
|
145
|
+
} else t7 = $[36];
|
|
146
|
+
return t7;
|
|
147
|
+
}
|
|
148
|
+
function sourceText(value) {
|
|
149
|
+
if (typeof value === "string") return value;
|
|
150
|
+
if (value !== null && typeof value === "object") return Object.values(value).find(isString) ?? null;
|
|
151
|
+
return null;
|
|
152
|
+
}
|
|
153
|
+
/** The `slug` type: `from` names the field it follows, and setting `prefix` shows a URL prefix. */
|
|
154
|
+
function SlugFieldEntry(props) {
|
|
155
|
+
const $ = c(18);
|
|
156
|
+
const { field, parentValue } = props;
|
|
157
|
+
let t0;
|
|
158
|
+
if ($[0] !== field) {
|
|
159
|
+
t0 = getSetting(field, "prefix", isString);
|
|
160
|
+
$[0] = field;
|
|
161
|
+
$[1] = t0;
|
|
162
|
+
} else t0 = $[1];
|
|
163
|
+
const prefix = t0;
|
|
164
|
+
let t1;
|
|
165
|
+
if ($[2] !== field.from || $[3] !== parentValue) {
|
|
166
|
+
t1 = field.from !== void 0 && parentValue !== null && typeof parentValue === "object" ? sourceText(parentValue[field.from]) : void 0;
|
|
167
|
+
$[2] = field.from;
|
|
168
|
+
$[3] = parentValue;
|
|
169
|
+
$[4] = t1;
|
|
170
|
+
} else t1 = $[4];
|
|
171
|
+
const source = t1;
|
|
172
|
+
let t2;
|
|
173
|
+
if ($[5] !== props) {
|
|
174
|
+
t2 = getFieldProps(props);
|
|
175
|
+
$[5] = props;
|
|
176
|
+
$[6] = t2;
|
|
177
|
+
} else t2 = $[6];
|
|
178
|
+
const t3 = isString(props.value) ? props.value : null;
|
|
179
|
+
let t4;
|
|
180
|
+
if ($[7] !== field.from || $[8] !== source) {
|
|
181
|
+
t4 = source !== void 0 ? {
|
|
182
|
+
source,
|
|
183
|
+
sourceLabel: field.from ?? ""
|
|
184
|
+
} : {};
|
|
185
|
+
$[7] = field.from;
|
|
186
|
+
$[8] = source;
|
|
187
|
+
$[9] = t4;
|
|
188
|
+
} else t4 = $[9];
|
|
189
|
+
let t5;
|
|
190
|
+
if ($[10] !== prefix) {
|
|
191
|
+
t5 = prefix !== void 0 ? { prefix } : {};
|
|
192
|
+
$[10] = prefix;
|
|
193
|
+
$[11] = t5;
|
|
194
|
+
} else t5 = $[11];
|
|
195
|
+
let t6;
|
|
196
|
+
if ($[12] !== props.onChange || $[13] !== t2 || $[14] !== t3 || $[15] !== t4 || $[16] !== t5) {
|
|
197
|
+
t6 = /* @__PURE__ */ jsx(SlugField, {
|
|
198
|
+
...t2,
|
|
199
|
+
value: t3,
|
|
200
|
+
onChange: props.onChange,
|
|
201
|
+
...t4,
|
|
202
|
+
...t5
|
|
203
|
+
});
|
|
204
|
+
$[12] = props.onChange;
|
|
205
|
+
$[13] = t2;
|
|
206
|
+
$[14] = t3;
|
|
207
|
+
$[15] = t4;
|
|
208
|
+
$[16] = t5;
|
|
209
|
+
$[17] = t6;
|
|
210
|
+
} else t6 = $[17];
|
|
211
|
+
return t6;
|
|
212
|
+
}
|
|
213
|
+
//#endregion
|
|
214
|
+
export { SlugField, SlugFieldEntry, slugify };
|
|
215
|
+
|
|
216
|
+
//# sourceMappingURL=SlugField.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SlugField.js","names":["next","WandSparkles","value","onChange","disabled","readOnly","_c"],"sources":["../../src/slug/SlugField.tsx"],"sourcesContent":["import { WandSparkles } from 'lucide-react';\nimport { useEffect, useRef } from 'react';\n\nimport { IconButton, usePanneauIntl } from '@panneau/ui';\n\nimport { getFieldProps, getSetting, isString } from '../shared/entry';\nimport { messages } from '../shared/messages';\nimport type { FieldEntryProps } from '../shared/types';\nimport { TextField, type TextFieldProps } from '../text/TextField';\n\n/**\n * Turns a text into a slug: lowercase ASCII letters and digits separated by dashes, without\n * accents (`Été à Montréal` gives `ete-a-montreal`).\n */\nexport function slugify(text: string): string {\n return text\n .normalize('NFKD')\n .replace(/[̀-ͯ]/g, '')\n .toLowerCase()\n .replace(/[^a-z0-9]+/g, '-')\n .replace(/^-+|-+$/g, '');\n}\n\nexport interface SlugFieldProps extends Omit<TextFieldProps, 'type' | 'suffix'> {\n /**\n * The text the slug is made from (a title). While the slug is empty or still matches it, the\n * slug follows it.\n */\n source?: string | null;\n /** Name of the source, for the button that generates the slug from it. */\n sourceLabel?: string;\n}\n\n/**\n * A slug for URLs. Typed text is cleaned up on blur. With a `source`, the slug follows it until\n * it is edited, and a button generates it again.\n */\nexport function SlugField({\n value,\n onChange,\n onBlur,\n source,\n sourceLabel,\n disabled,\n readOnly,\n ...props\n}: SlugFieldProps) {\n const intl = usePanneauIntl();\n const previousSource = useRef(source);\n const editable = disabled !== true && readOnly !== true;\n\n useEffect(() => {\n const previous = previousSource.current;\n previousSource.current = source;\n if (!editable || source === previous || source === undefined) {\n return;\n }\n const current = value ?? '';\n if (current === '' || current === slugify(previous ?? '')) {\n const next = slugify(source ?? '');\n if (next !== current) {\n onChange?.(next === '' ? null : next);\n }\n }\n }, [source, value, onChange, editable]);\n\n const generated = slugify(source ?? '');\n return (\n <TextField\n autoComplete=\"off\"\n {...props}\n value={value}\n onChange={onChange}\n disabled={disabled}\n readOnly={readOnly}\n onBlur={() => {\n if (value !== null && value !== undefined) {\n const next = slugify(value);\n if (next !== value) {\n onChange?.(next === '' ? null : next);\n }\n }\n onBlur?.();\n }}\n suffix={\n source !== undefined && editable ? (\n <IconButton\n icon={WandSparkles}\n size=\"sm\"\n label={intl.formatMessage(messages.generateSlug, {\n field: sourceLabel ?? '',\n })}\n isDisabled={generated === '' || generated === value}\n onPress={() => onChange?.(generated === '' ? null : generated)}\n />\n ) : undefined\n }\n />\n );\n}\n\nfunction sourceText(value: unknown): string | null {\n if (typeof value === 'string') {\n return value;\n }\n // A localized source: its first locale.\n if (value !== null && typeof value === 'object') {\n const first = Object.values(value as Record<string, unknown>).find(isString);\n return first ?? null;\n }\n return null;\n}\n\n/** The `slug` type: `from` names the field it follows, and setting `prefix` shows a URL prefix. */\nexport function SlugFieldEntry(props: FieldEntryProps) {\n const { field, parentValue } = props;\n const prefix = getSetting(field, 'prefix', isString);\n const source =\n field.from !== undefined && parentValue !== null && typeof parentValue === 'object'\n ? sourceText((parentValue as Record<string, unknown>)[field.from])\n : undefined;\n return (\n <SlugField\n {...getFieldProps(props)}\n value={isString(props.value) ? props.value : null}\n onChange={props.onChange}\n {...(source !== undefined ? { source, sourceLabel: field.from ?? '' } : {})}\n {...(prefix !== undefined ? { prefix } : {})}\n />\n );\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/slug/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,cAAc,EAAE,KAAK,cAAc,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { SlugField, SlugFieldEntry, slugify } from "./SlugField.js";
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { ReactNode } from 'react';
|
|
2
|
+
import type { FieldEntryProps, FieldProps } from '../shared/types';
|
|
3
|
+
export interface TextFieldProps extends FieldProps<string> {
|
|
4
|
+
placeholder?: string;
|
|
5
|
+
/** Shows a count of characters against this maximum. It doesn't stop typing. */
|
|
6
|
+
maxLength?: number;
|
|
7
|
+
/** Content before the text, inside the control. */
|
|
8
|
+
prefix?: ReactNode;
|
|
9
|
+
/** Content after the text, inside the control. */
|
|
10
|
+
suffix?: ReactNode;
|
|
11
|
+
/** Type of the input. Defaults to `text`. */
|
|
12
|
+
type?: 'text' | 'email' | 'url' | 'tel' | 'password' | 'search';
|
|
13
|
+
inputMode?: 'text' | 'email' | 'url' | 'tel' | 'search' | 'numeric' | 'decimal' | 'none';
|
|
14
|
+
autoComplete?: string;
|
|
15
|
+
controlClassName?: string;
|
|
16
|
+
inputClassName?: string;
|
|
17
|
+
}
|
|
18
|
+
/** The description of a text field, with its character count. */
|
|
19
|
+
export declare function useCountedDescription(description: ReactNode, value: string | null | undefined, maxLength: number | undefined): ReactNode;
|
|
20
|
+
/** A single-line text. An empty text is `null`. */
|
|
21
|
+
export declare function TextField({ value, onChange, description, maxLength, disabled, readOnly, required, ...props }: TextFieldProps): import("react").JSX.Element;
|
|
22
|
+
/** The `text` type: settings `prefix` and `suffix`, and a character count from the `max` rule. */
|
|
23
|
+
export declare function TextFieldEntry(props: FieldEntryProps): import("react").JSX.Element;
|
|
24
|
+
//# sourceMappingURL=TextField.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"TextField.d.ts","sourceRoot":"","sources":["../../src/text/TextField.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAMvC,OAAO,KAAK,EAAE,eAAe,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAEnE,MAAM,WAAW,cAAe,SAAQ,UAAU,CAAC,MAAM,CAAC;IACtD,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,gFAAgF;IAChF,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,mDAAmD;IACnD,MAAM,CAAC,EAAE,SAAS,CAAC;IACnB,kDAAkD;IAClD,MAAM,CAAC,EAAE,SAAS,CAAC;IACnB,6CAA6C;IAC7C,IAAI,CAAC,EAAE,MAAM,GAAG,OAAO,GAAG,KAAK,GAAG,KAAK,GAAG,UAAU,GAAG,QAAQ,CAAC;IAChE,SAAS,CAAC,EAAE,MAAM,GAAG,OAAO,GAAG,KAAK,GAAG,KAAK,GAAG,QAAQ,GAAG,SAAS,GAAG,SAAS,GAAG,MAAM,CAAC;IACzF,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,cAAc,CAAC,EAAE,MAAM,CAAC;CAC3B;AAED,iEAAiE;AACjE,wBAAgB,qBAAqB,CACjC,WAAW,EAAE,SAAS,EACtB,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,EAChC,SAAS,EAAE,MAAM,GAAG,SAAS,GAC9B,SAAS,CAiBX;AAED,mDAAmD;AACnD,wBAAgB,SAAS,CAAC,EACtB,KAAK,EACL,QAAQ,EACR,WAAW,EACX,SAAS,EACT,QAAQ,EACR,QAAQ,EACR,QAAQ,EACR,GAAG,KAAK,EACX,EAAE,cAAc,+BAahB;AAED,kGAAkG;AAClG,wBAAgB,cAAc,CAAC,KAAK,EAAE,eAAe,+BAepD"}
|