@panneau/core 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.
Files changed (75) hide show
  1. package/README.md +83 -0
  2. package/dist/definition/abilities.d.ts +21 -0
  3. package/dist/definition/abilities.d.ts.map +1 -0
  4. package/dist/definition/abilities.js +47 -0
  5. package/dist/definition/abilities.js.map +1 -0
  6. package/dist/definition/form.d.ts +31 -0
  7. package/dist/definition/form.d.ts.map +1 -0
  8. package/dist/definition/form.js +54 -0
  9. package/dist/definition/form.js.map +1 -0
  10. package/dist/definition/list.d.ts +44 -0
  11. package/dist/definition/list.d.ts.map +1 -0
  12. package/dist/definition/list.js +70 -0
  13. package/dist/definition/list.js.map +1 -0
  14. package/dist/definition/resource.d.ts +21 -0
  15. package/dist/definition/resource.d.ts.map +1 -0
  16. package/dist/definition/resource.js +24 -0
  17. package/dist/definition/resource.js.map +1 -0
  18. package/dist/form/form.d.ts +148 -0
  19. package/dist/form/form.d.ts.map +1 -0
  20. package/dist/form/form.js +239 -0
  21. package/dist/form/form.js.map +1 -0
  22. package/dist/form/messages.d.ts +7 -0
  23. package/dist/form/messages.d.ts.map +1 -0
  24. package/dist/form/messages.js +25 -0
  25. package/dist/form/messages.js.map +1 -0
  26. package/dist/form/problem.d.ts +16 -0
  27. package/dist/form/problem.d.ts.map +1 -0
  28. package/dist/form/problem.js +34 -0
  29. package/dist/form/problem.js.map +1 -0
  30. package/dist/form/selectors.d.ts +16 -0
  31. package/dist/form/selectors.d.ts.map +1 -0
  32. package/dist/form/selectors.js +27 -0
  33. package/dist/form/selectors.js.map +1 -0
  34. package/dist/form/store.d.ts +19 -0
  35. package/dist/form/store.d.ts.map +1 -0
  36. package/dist/form/store.js +29 -0
  37. package/dist/form/store.js.map +1 -0
  38. package/dist/index.d.ts +17 -692
  39. package/dist/index.d.ts.map +1 -0
  40. package/dist/index.js +17 -536
  41. package/dist/list/list.d.ts +68 -0
  42. package/dist/list/list.d.ts.map +1 -0
  43. package/dist/list/list.js +191 -0
  44. package/dist/list/list.js.map +1 -0
  45. package/dist/list/router.d.ts +26 -0
  46. package/dist/list/router.d.ts.map +1 -0
  47. package/dist/list/router.js +54 -0
  48. package/dist/list/router.js.map +1 -0
  49. package/dist/plugins.d.ts +73 -0
  50. package/dist/plugins.d.ts.map +1 -0
  51. package/dist/plugins.js +54 -0
  52. package/dist/plugins.js.map +1 -0
  53. package/dist/react/provider.d.ts +38 -0
  54. package/dist/react/provider.d.ts.map +1 -0
  55. package/dist/react/provider.js +63 -0
  56. package/dist/react/provider.js.map +1 -0
  57. package/dist/react/useForm.d.ts +26 -0
  58. package/dist/react/useForm.d.ts.map +1 -0
  59. package/dist/react/useForm.js +115 -0
  60. package/dist/react/useForm.js.map +1 -0
  61. package/dist/react/useList.d.ts +11 -0
  62. package/dist/react/useList.d.ts.map +1 -0
  63. package/dist/react/useList.js +47 -0
  64. package/dist/react/useList.js.map +1 -0
  65. package/dist/values.d.ts +12 -0
  66. package/dist/values.d.ts.map +1 -0
  67. package/dist/values.js +44 -0
  68. package/dist/values.js.map +1 -0
  69. package/package.json +19 -81
  70. package/dist/contexts.d.ts +0 -667
  71. package/dist/contexts.js +0 -1772
  72. package/dist/hooks.d.ts +0 -512
  73. package/dist/hooks.js +0 -2146
  74. package/dist/utils.d.ts +0 -457
  75. package/dist/utils.js +0 -557
@@ -0,0 +1,191 @@
1
+ import { isEqual } from "../values.js";
2
+ import { fromSearchParams, isFilterRange, toSearchParams } from "@panneau/protocol/runtime";
3
+ //#region src/list/list.ts
4
+ var EMPTY_SELECTION = {
5
+ mode: "ids",
6
+ ids: []
7
+ };
8
+ var LIST_PARAM = /^(page|perPage|sort|q|fields|filter\[.+)$/;
9
+ /** Removes the values that are the defaults, so the URL only keeps what the user changed. */
10
+ function normalize(query, defaults) {
11
+ const result = {};
12
+ if (query.page !== void 0 && query.page > 1) result.page = query.page;
13
+ if (query.perPage !== void 0 && query.perPage !== defaults.perPage) result.perPage = query.perPage;
14
+ if (query.sort !== void 0 && !isEqual(query.sort, defaults.sort)) result.sort = [...query.sort];
15
+ const filter = Object.fromEntries(Object.entries(query.filter ?? {}).filter(([, value]) => value !== "" && !(Array.isArray(value) && value.length === 0) && !(isFilterRange(value) && Object.keys(value).length === 0)));
16
+ if (Object.keys(filter).length > 0) result.filter = filter;
17
+ if (query.q !== void 0 && query.q !== "") result.q = query.q;
18
+ if (query.fields !== void 0) result.fields = query.fields;
19
+ return result;
20
+ }
21
+ /** Creates a headless list. */
22
+ function createList(options = {}) {
23
+ const defaults = {
24
+ perPage: options.defaults?.perPage ?? options.list?.perPage ?? 20,
25
+ sort: options.defaults?.sort ?? options.list?.defaultSort ?? []
26
+ };
27
+ const router = options.router ?? null;
28
+ const readRouter = () => router !== null ? normalize(fromSearchParams(new URLSearchParams(router.getSearch())), defaults) : {};
29
+ let params = router !== null ? readRouter() : normalize(options.query ?? {}, defaults);
30
+ let selection = EMPTY_SELECTION;
31
+ const compute = () => ({
32
+ query: {
33
+ ...params,
34
+ page: params.page ?? 1,
35
+ perPage: params.perPage ?? defaults.perPage,
36
+ ...params.sort !== void 0 || defaults.sort.length > 0 ? { sort: params.sort ?? defaults.sort } : {}
37
+ },
38
+ selection
39
+ });
40
+ let state = compute();
41
+ const listeners = /* @__PURE__ */ new Set();
42
+ const notify = () => {
43
+ state = compute();
44
+ listeners.forEach((listener) => listener());
45
+ };
46
+ const writeRouter = (replace) => {
47
+ if (router === null) return;
48
+ const search = new URLSearchParams(router.getSearch());
49
+ [...search.keys()].filter((key) => LIST_PARAM.test(key)).forEach((key) => search.delete(key));
50
+ toSearchParams(params).forEach((value, key) => search.append(key, value));
51
+ const next = search.toString();
52
+ if (next !== router.getSearch()) router.setSearch(next, { replace });
53
+ };
54
+ const setParams = (next, { replace = false } = {}) => {
55
+ const normalized = normalize(next, defaults);
56
+ if (isEqual(normalized, params)) return;
57
+ const scopeChanged = !isEqual(normalized.filter, params.filter) || normalized.q !== params.q;
58
+ params = normalized;
59
+ if (scopeChanged) selection = EMPTY_SELECTION;
60
+ writeRouter(replace);
61
+ notify();
62
+ };
63
+ const setSelection = (next) => {
64
+ selection = next;
65
+ notify();
66
+ };
67
+ let unsubscribeRouter = null;
68
+ const onRouterChange = () => {
69
+ const next = readRouter();
70
+ if (isEqual(next, params)) return;
71
+ if (!isEqual(next.filter, params.filter) || next.q !== params.q) selection = EMPTY_SELECTION;
72
+ params = next;
73
+ notify();
74
+ };
75
+ const list = {
76
+ getState() {
77
+ if (router !== null && unsubscribeRouter === null) onRouterChange();
78
+ return state;
79
+ },
80
+ subscribe(listener) {
81
+ listeners.add(listener);
82
+ if (router !== null && unsubscribeRouter === null) {
83
+ unsubscribeRouter = router.subscribe(onRouterChange);
84
+ onRouterChange();
85
+ }
86
+ return () => {
87
+ listeners.delete(listener);
88
+ if (listeners.size === 0 && unsubscribeRouter !== null) {
89
+ unsubscribeRouter();
90
+ unsubscribeRouter = null;
91
+ }
92
+ };
93
+ },
94
+ setPage: (page) => setParams({
95
+ ...params,
96
+ page
97
+ }),
98
+ setPerPage: (perPage) => setParams({
99
+ ...params,
100
+ perPage,
101
+ page: 1
102
+ }),
103
+ setSort: (sort) => setParams({
104
+ ...params,
105
+ sort: [...sort],
106
+ page: 1
107
+ }),
108
+ toggleSort(field) {
109
+ const sort = state.query.sort ?? [];
110
+ if (sort[0] === field) setParams({
111
+ ...params,
112
+ sort: [`-${field}`],
113
+ page: 1
114
+ });
115
+ else if (sort[0] === `-${field}` && params.sort !== void 0) {
116
+ const { sort: _sort, ...rest } = params;
117
+ setParams({
118
+ ...rest,
119
+ page: 1
120
+ });
121
+ } else setParams({
122
+ ...params,
123
+ sort: [field],
124
+ page: 1
125
+ });
126
+ },
127
+ setFilter(id, value) {
128
+ const { [id]: _removed, ...filter } = params.filter ?? {};
129
+ setParams({
130
+ ...params,
131
+ filter: value === void 0 ? filter : {
132
+ ...filter,
133
+ [id]: value
134
+ },
135
+ page: 1
136
+ });
137
+ },
138
+ setSearch: (q) => setParams({
139
+ ...params,
140
+ q,
141
+ page: 1
142
+ }, { replace: true }),
143
+ resetQuery: () => setParams({}),
144
+ select(ids) {
145
+ if (selection.mode === "all") setSelection({
146
+ mode: "all",
147
+ except: selection.except.filter((id) => !ids.includes(id))
148
+ });
149
+ else setSelection({
150
+ mode: "ids",
151
+ ids: [.../* @__PURE__ */ new Set([...selection.ids, ...ids])]
152
+ });
153
+ },
154
+ deselect(ids) {
155
+ if (selection.mode === "all") setSelection({
156
+ mode: "all",
157
+ except: [.../* @__PURE__ */ new Set([...selection.except, ...ids])]
158
+ });
159
+ else setSelection({
160
+ mode: "ids",
161
+ ids: selection.ids.filter((id) => !ids.includes(id))
162
+ });
163
+ },
164
+ toggle(id) {
165
+ if (list.isSelected(id)) list.deselect([id]);
166
+ else list.select([id]);
167
+ },
168
+ selectAllMatching: () => setSelection({
169
+ mode: "all",
170
+ except: []
171
+ }),
172
+ clearSelection: () => setSelection(EMPTY_SELECTION),
173
+ isSelected: (id) => selection.mode === "all" ? !selection.except.includes(id) : selection.ids.includes(id),
174
+ getSelectedCount: (total) => selection.mode === "all" ? Math.max(0, total - selection.except.length) : selection.ids.length,
175
+ getSelection() {
176
+ if (selection.mode === "ids") return selection.ids.length > 0 ? { ids: [...selection.ids] } : null;
177
+ const query = {};
178
+ if (params.filter !== void 0) query.filter = params.filter;
179
+ if (params.q !== void 0) query.q = params.q;
180
+ return selection.except.length > 0 ? {
181
+ query,
182
+ except: [...selection.except]
183
+ } : { query };
184
+ }
185
+ };
186
+ return list;
187
+ }
188
+ //#endregion
189
+ export { createList };
190
+
191
+ //# sourceMappingURL=list.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"list.js","names":[],"sources":["../../src/list/list.ts"],"sourcesContent":["import type { FilterValue, ListQuery, Selection } from '@panneau/protocol';\nimport { fromSearchParams, isFilterRange, toSearchParams } from '@panneau/protocol/runtime';\n\nimport type { ResolvedList } from '../definition/list';\nimport { isEqual } from '../values';\nimport type { Router } from './router';\n\n/**\n * Selected items: some ids, or every item matching the list query (filters and search) except\n * some ids. \"All matching\" covers the items of every page.\n */\nexport type ListSelection =\n { mode: 'ids'; ids: readonly string[] } | { mode: 'all'; except: readonly string[] };\n\nexport interface ListState {\n /** The query to fetch: the list's defaults, overridden by the current state. */\n query: ListQuery;\n selection: ListSelection;\n}\n\nexport interface ListDefaults {\n perPage?: number;\n /** Default sort, as `sort` query values (`['-published_at']`). */\n sort?: string[];\n}\n\nexport interface ListOptions {\n /** A resolved list, for the defaults (`perPage`, `defaultSort`). */\n list?: ResolvedList;\n /** Defaults, over those of `list`. Default values are not written to the URL. */\n defaults?: ListDefaults;\n /** The starting query, when there is no router. */\n query?: ListQuery;\n /** Keeps the query in the URL. The router's query string wins over `query`. */\n router?: Router | null;\n}\n\n/** A headless list: query and selection. React components use `useList()`. */\nexport interface List {\n getState(): ListState;\n subscribe(listener: () => void): () => void;\n setPage(page: number): void;\n /** Sets the page size, and goes back to the first page. */\n setPerPage(perPage: number): void;\n /** Sets the sort (`['-published_at', 'title']`), and goes back to the first page. */\n setSort(sort: readonly string[]): void;\n /** Sorts by a field: ascending, then descending, then back to the default sort. */\n toggleSort(field: string): void;\n /** Sets or removes (`undefined`) a filter, goes back to the first page, and clears the selection. */\n setFilter(id: string, value: FilterValue | undefined): void;\n /** Sets the search, goes back to the first page, and clears the selection. */\n setSearch(q: string): void;\n /** Removes the page, filters, search and sort: back to the defaults. */\n resetQuery(): void;\n /** Adds ids to the selection (removes them from the exceptions in \"all matching\" mode). */\n select(ids: readonly string[]): void;\n /** Removes ids from the selection (adds them to the exceptions in \"all matching\" mode). */\n deselect(ids: readonly string[]): void;\n toggle(id: string): void;\n /** Selects every item matching the query, on every page. */\n selectAllMatching(): void;\n clearSelection(): void;\n isSelected(id: string): boolean;\n /** How many items are selected, given the total of the list. */\n getSelectedCount(total: number): number;\n /** The selection to send with an action, or `null` when nothing is selected. */\n getSelection(): Selection | null;\n}\n\nconst EMPTY_SELECTION: ListSelection = { mode: 'ids', ids: [] };\nconst LIST_PARAM = /^(page|perPage|sort|q|fields|filter\\[.+)$/;\n\n/** Removes the values that are the defaults, so the URL only keeps what the user changed. */\nfunction normalize(query: ListQuery, defaults: Required<ListDefaults>): ListQuery {\n const result: ListQuery = {};\n if (query.page !== undefined && query.page > 1) {\n result.page = query.page;\n }\n if (query.perPage !== undefined && query.perPage !== defaults.perPage) {\n result.perPage = query.perPage;\n }\n if (query.sort !== undefined && !isEqual(query.sort, defaults.sort)) {\n result.sort = [...query.sort];\n }\n const filter = Object.fromEntries(\n Object.entries(query.filter ?? {}).filter(\n ([, value]) =>\n value !== '' &&\n !(Array.isArray(value) && value.length === 0) &&\n !(isFilterRange(value) && Object.keys(value).length === 0),\n ),\n );\n if (Object.keys(filter).length > 0) {\n result.filter = filter;\n }\n if (query.q !== undefined && query.q !== '') {\n result.q = query.q;\n }\n if (query.fields !== undefined) {\n result.fields = query.fields;\n }\n return result;\n}\n\n/** Creates a headless list. */\nexport function createList(options: ListOptions = {}): List {\n const defaults: Required<ListDefaults> = {\n perPage: options.defaults?.perPage ?? options.list?.perPage ?? 20,\n sort: options.defaults?.sort ?? options.list?.defaultSort ?? [],\n };\n const router = options.router ?? null;\n const readRouter = (): ListQuery =>\n router !== null\n ? normalize(fromSearchParams(new URLSearchParams(router.getSearch())), defaults)\n : {};\n\n let params: ListQuery =\n router !== null ? readRouter() : normalize(options.query ?? {}, defaults);\n let selection: ListSelection = EMPTY_SELECTION;\n\n const compute = (): ListState => ({\n query: {\n ...params,\n page: params.page ?? 1,\n perPage: params.perPage ?? defaults.perPage,\n ...(params.sort !== undefined || defaults.sort.length > 0\n ? { sort: params.sort ?? defaults.sort }\n : {}),\n },\n selection,\n });\n\n let state = compute();\n const listeners = new Set<() => void>();\n const notify = () => {\n state = compute();\n listeners.forEach((listener) => listener());\n };\n\n const writeRouter = (replace: boolean) => {\n if (router === null) {\n return;\n }\n const search = new URLSearchParams(router.getSearch());\n [...search.keys()]\n .filter((key) => LIST_PARAM.test(key))\n .forEach((key) => search.delete(key));\n toSearchParams(params).forEach((value, key) => search.append(key, value));\n const next = search.toString();\n if (next !== router.getSearch()) {\n router.setSearch(next, { replace });\n }\n };\n\n const setParams = (next: ListQuery, { replace = false } = {}) => {\n const normalized = normalize(next, defaults);\n if (isEqual(normalized, params)) {\n return;\n }\n const scopeChanged =\n !isEqual(normalized.filter, params.filter) || normalized.q !== params.q;\n params = normalized;\n if (scopeChanged) {\n selection = EMPTY_SELECTION;\n }\n writeRouter(replace);\n notify();\n };\n\n const setSelection = (next: ListSelection) => {\n selection = next;\n notify();\n };\n\n let unsubscribeRouter: (() => void) | null = null;\n const onRouterChange = () => {\n const next = readRouter();\n if (isEqual(next, params)) {\n return;\n }\n if (!isEqual(next.filter, params.filter) || next.q !== params.q) {\n selection = EMPTY_SELECTION;\n }\n params = next;\n notify();\n };\n\n const list: List = {\n getState() {\n if (router !== null && unsubscribeRouter === null) {\n onRouterChange();\n }\n return state;\n },\n subscribe(listener) {\n listeners.add(listener);\n if (router !== null && unsubscribeRouter === null) {\n unsubscribeRouter = router.subscribe(onRouterChange);\n onRouterChange();\n }\n return () => {\n listeners.delete(listener);\n if (listeners.size === 0 && unsubscribeRouter !== null) {\n unsubscribeRouter();\n unsubscribeRouter = null;\n }\n };\n },\n setPage: (page) => setParams({ ...params, page }),\n setPerPage: (perPage) => setParams({ ...params, perPage, page: 1 }),\n setSort: (sort) => setParams({ ...params, sort: [...sort], page: 1 }),\n toggleSort(field) {\n const sort = state.query.sort ?? [];\n if (sort[0] === field) {\n setParams({ ...params, sort: [`-${field}`], page: 1 });\n } else if (sort[0] === `-${field}` && params.sort !== undefined) {\n const { sort: _sort, ...rest } = params;\n setParams({ ...rest, page: 1 });\n } else {\n setParams({ ...params, sort: [field], page: 1 });\n }\n },\n setFilter(id, value) {\n const { [id]: _removed, ...filter } = params.filter ?? {};\n setParams({\n ...params,\n filter: value === undefined ? filter : { ...filter, [id]: value },\n page: 1,\n });\n },\n setSearch: (q) => setParams({ ...params, q, page: 1 }, { replace: true }),\n resetQuery: () => setParams({}),\n select(ids) {\n if (selection.mode === 'all') {\n setSelection({\n mode: 'all',\n except: selection.except.filter((id) => !ids.includes(id)),\n });\n } else {\n setSelection({ mode: 'ids', ids: [...new Set([...selection.ids, ...ids])] });\n }\n },\n deselect(ids) {\n if (selection.mode === 'all') {\n setSelection({ mode: 'all', except: [...new Set([...selection.except, ...ids])] });\n } else {\n setSelection({ mode: 'ids', ids: selection.ids.filter((id) => !ids.includes(id)) });\n }\n },\n toggle(id) {\n if (list.isSelected(id)) {\n list.deselect([id]);\n } else {\n list.select([id]);\n }\n },\n selectAllMatching: () => setSelection({ mode: 'all', except: [] }),\n clearSelection: () => setSelection(EMPTY_SELECTION),\n isSelected: (id) =>\n selection.mode === 'all' ? !selection.except.includes(id) : selection.ids.includes(id),\n getSelectedCount: (total) =>\n selection.mode === 'all'\n ? Math.max(0, total - selection.except.length)\n : selection.ids.length,\n getSelection() {\n if (selection.mode === 'ids') {\n return selection.ids.length > 0 ? { ids: [...selection.ids] } : null;\n }\n const query: { filter?: Record<string, unknown>; q?: string } = {};\n if (params.filter !== undefined) {\n query.filter = params.filter;\n }\n if (params.q !== undefined) {\n query.q = params.q;\n }\n return selection.except.length > 0\n ? { query, except: [...selection.except] }\n : { query };\n },\n };\n return list;\n}\n"],"mappings":"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"}
@@ -0,0 +1,26 @@
1
+ /**
2
+ * The part of a router that lists need: read and write the query string, and know when it
3
+ * changes (back and forward). An app adapts its own router to it.
4
+ */
5
+ export interface Router {
6
+ /** The current query string, without `?`. */
7
+ getSearch(): string;
8
+ /** Navigates to the same path with another query string. `replace` doesn't add a history entry. */
9
+ setSearch(search: string, options?: {
10
+ replace?: boolean;
11
+ }): void;
12
+ /** Calls `listener` when the query string changes. Returns a function that unsubscribes. */
13
+ subscribe(listener: () => void): () => void;
14
+ }
15
+ /** A router over `window.history` and `window.location`. */
16
+ export declare function createHistoryRouter(target?: Window): Router;
17
+ /** A router that keeps the query string in memory, for tests and embedded lists. */
18
+ export interface MemoryRouter extends Router {
19
+ /** Query strings of the history entries, the last one being the current one. */
20
+ history: string[];
21
+ /** Goes back to the previous entry, like the browser's back button. */
22
+ back(): void;
23
+ }
24
+ /** Creates a router that keeps the query string in memory. */
25
+ export declare function createMemoryRouter(initialSearch?: string): MemoryRouter;
26
+ //# sourceMappingURL=router.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"router.d.ts","sourceRoot":"","sources":["../../src/list/router.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,MAAM,WAAW,MAAM;IACnB,6CAA6C;IAC7C,SAAS,IAAI,MAAM,CAAC;IACpB,mGAAmG;IACnG,SAAS,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE;QAAE,OAAO,CAAC,EAAE,OAAO,CAAA;KAAE,GAAG,IAAI,CAAC;IACjE,4FAA4F;IAC5F,SAAS,CAAC,QAAQ,EAAE,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC;CAC/C;AAED,4DAA4D;AAC5D,wBAAgB,mBAAmB,CAAC,MAAM,GAAE,MAAe,GAAG,MAAM,CAuBnE;AAED,oFAAoF;AACpF,MAAM,WAAW,YAAa,SAAQ,MAAM;IACxC,gFAAgF;IAChF,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,uEAAuE;IACvE,IAAI,IAAI,IAAI,CAAC;CAChB;AAED,8DAA8D;AAC9D,wBAAgB,kBAAkB,CAAC,aAAa,SAAK,GAAG,YAAY,CA4BnE"}
@@ -0,0 +1,54 @@
1
+ //#region src/list/router.ts
2
+ /** A router over `window.history` and `window.location`. */
3
+ function createHistoryRouter(target = window) {
4
+ const listeners = /* @__PURE__ */ new Set();
5
+ const notify = () => listeners.forEach((listener) => listener());
6
+ return {
7
+ getSearch: () => target.location.search.replace(/^\?/, ""),
8
+ setSearch(search, { replace = false } = {}) {
9
+ const url = `${target.location.pathname}${search !== "" ? `?${search}` : ""}${target.location.hash}`;
10
+ if (replace) target.history.replaceState(target.history.state, "", url);
11
+ else target.history.pushState(target.history.state, "", url);
12
+ notify();
13
+ },
14
+ subscribe(listener) {
15
+ listeners.add(listener);
16
+ target.addEventListener("popstate", listener);
17
+ return () => {
18
+ listeners.delete(listener);
19
+ target.removeEventListener("popstate", listener);
20
+ };
21
+ }
22
+ };
23
+ }
24
+ /** Creates a router that keeps the query string in memory. */
25
+ function createMemoryRouter(initialSearch = "") {
26
+ const history = [initialSearch];
27
+ const listeners = /* @__PURE__ */ new Set();
28
+ const notify = () => listeners.forEach((listener) => listener());
29
+ return {
30
+ history,
31
+ getSearch: () => history.at(-1) ?? "",
32
+ back() {
33
+ if (history.length > 1) {
34
+ history.pop();
35
+ notify();
36
+ }
37
+ },
38
+ setSearch(search, { replace = false } = {}) {
39
+ if (replace) history[history.length - 1] = search;
40
+ else history.push(search);
41
+ notify();
42
+ },
43
+ subscribe(listener) {
44
+ listeners.add(listener);
45
+ return () => {
46
+ listeners.delete(listener);
47
+ };
48
+ }
49
+ };
50
+ }
51
+ //#endregion
52
+ export { createHistoryRouter, createMemoryRouter };
53
+
54
+ //# sourceMappingURL=router.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"router.js","names":[],"sources":["../../src/list/router.ts"],"sourcesContent":["/**\n * The part of a router that lists need: read and write the query string, and know when it\n * changes (back and forward). An app adapts its own router to it.\n */\nexport interface Router {\n /** The current query string, without `?`. */\n getSearch(): string;\n /** Navigates to the same path with another query string. `replace` doesn't add a history entry. */\n setSearch(search: string, options?: { replace?: boolean }): void;\n /** Calls `listener` when the query string changes. Returns a function that unsubscribes. */\n subscribe(listener: () => void): () => void;\n}\n\n/** A router over `window.history` and `window.location`. */\nexport function createHistoryRouter(target: Window = window): Router {\n const listeners = new Set<() => void>();\n const notify = () => listeners.forEach((listener) => listener());\n return {\n getSearch: () => target.location.search.replace(/^\\?/, ''),\n setSearch(search, { replace = false } = {}) {\n const url = `${target.location.pathname}${search !== '' ? `?${search}` : ''}${target.location.hash}`;\n if (replace) {\n target.history.replaceState(target.history.state, '', url);\n } else {\n target.history.pushState(target.history.state, '', url);\n }\n notify();\n },\n subscribe(listener) {\n listeners.add(listener);\n target.addEventListener('popstate', listener);\n return () => {\n listeners.delete(listener);\n target.removeEventListener('popstate', listener);\n };\n },\n };\n}\n\n/** A router that keeps the query string in memory, for tests and embedded lists. */\nexport interface MemoryRouter extends Router {\n /** Query strings of the history entries, the last one being the current one. */\n history: string[];\n /** Goes back to the previous entry, like the browser's back button. */\n back(): void;\n}\n\n/** Creates a router that keeps the query string in memory. */\nexport function createMemoryRouter(initialSearch = ''): MemoryRouter {\n const history = [initialSearch];\n const listeners = new Set<() => void>();\n const notify = () => listeners.forEach((listener) => listener());\n return {\n history,\n getSearch: () => history.at(-1) ?? '',\n back() {\n if (history.length > 1) {\n history.pop();\n notify();\n }\n },\n setSearch(search, { replace = false } = {}) {\n if (replace) {\n history[history.length - 1] = search;\n } else {\n history.push(search);\n }\n notify();\n },\n subscribe(listener) {\n listeners.add(listener);\n return () => {\n listeners.delete(listener);\n };\n },\n };\n}\n"],"mappings":";;AAcA,SAAgB,oBAAoB,SAAiB,QAAgB;CACjE,MAAM,4BAAY,IAAI,IAAgB;CACtC,MAAM,eAAe,UAAU,SAAS,aAAa,SAAS,CAAC;CAC/D,OAAO;EACH,iBAAiB,OAAO,SAAS,OAAO,QAAQ,OAAO,EAAE;EACzD,UAAU,QAAQ,EAAE,UAAU,UAAU,CAAC,GAAG;GACxC,MAAM,MAAM,GAAG,OAAO,SAAS,WAAW,WAAW,KAAK,IAAI,WAAW,KAAK,OAAO,SAAS;GAC9F,IAAI,SACA,OAAO,QAAQ,aAAa,OAAO,QAAQ,OAAO,IAAI,GAAG;QAEzD,OAAO,QAAQ,UAAU,OAAO,QAAQ,OAAO,IAAI,GAAG;GAE1D,OAAO;EACX;EACA,UAAU,UAAU;GAChB,UAAU,IAAI,QAAQ;GACtB,OAAO,iBAAiB,YAAY,QAAQ;GAC5C,aAAa;IACT,UAAU,OAAO,QAAQ;IACzB,OAAO,oBAAoB,YAAY,QAAQ;GACnD;EACJ;CACJ;AACJ;;AAWA,SAAgB,mBAAmB,gBAAgB,IAAkB;CACjE,MAAM,UAAU,CAAC,aAAa;CAC9B,MAAM,4BAAY,IAAI,IAAgB;CACtC,MAAM,eAAe,UAAU,SAAS,aAAa,SAAS,CAAC;CAC/D,OAAO;EACH;EACA,iBAAiB,QAAQ,GAAG,EAAE,KAAK;EACnC,OAAO;GACH,IAAI,QAAQ,SAAS,GAAG;IACpB,QAAQ,IAAI;IACZ,OAAO;GACX;EACJ;EACA,UAAU,QAAQ,EAAE,UAAU,UAAU,CAAC,GAAG;GACxC,IAAI,SACA,QAAQ,QAAQ,SAAS,KAAK;QAE9B,QAAQ,KAAK,MAAM;GAEvB,OAAO;EACX;EACA,UAAU,UAAU;GAChB,UAAU,IAAI,QAAQ;GACtB,aAAa;IACT,UAAU,OAAO,QAAQ;GAC7B;EACJ;CACJ;AACJ"}
@@ -0,0 +1,73 @@
1
+ import type { ComponentType } from 'react';
2
+ /**
3
+ * A component provided by a plugin. Its props are defined by the package that renders it (fields,
4
+ * lists, app), so the registry stores it as given.
5
+ */
6
+ export type PluginComponent = ComponentType<any>;
7
+ /** UI messages of a locale, keyed by message id. */
8
+ export type Messages = Record<string, string>;
9
+ /** Messages, or a function that loads them (`() => import('./lang/fr.json')`). */
10
+ export type MessagesLoader = Messages | (() => Promise<Messages | {
11
+ default: Messages;
12
+ }>);
13
+ /** A theme: design tokens (CSS variables without `--`), with optional dark scheme overrides. */
14
+ export interface Theme {
15
+ label?: string;
16
+ /** Tokens of the light scheme, or of both schemes without `dark`: `{ 'panneau-color-accent': '#05f' }`. */
17
+ tokens: Record<string, string>;
18
+ /** Tokens that change in the dark scheme. */
19
+ dark?: Record<string, string>;
20
+ }
21
+ /** What a plugin adds to Panneau. Every part is keyed by the name the definition uses. */
22
+ export interface PanneauPlugin {
23
+ /** Unique id of the plugin. */
24
+ id: string;
25
+ /** Field components, by field type (`text`, `page-slug`). */
26
+ fields?: Record<string, PluginComponent>;
27
+ /** Components that display a value in lists, by display (`date`, `page-slug`). */
28
+ displays?: Record<string, PluginComponent>;
29
+ /** List layouts, by id (`table`, `cards`). */
30
+ listLayouts?: Record<string, PluginComponent>;
31
+ /** Handlers of `client` actions, by the action's `component`. */
32
+ actions?: Record<string, PluginComponent>;
33
+ /** Pages, by component name (`dashboard`). */
34
+ pages?: Record<string, PluginComponent>;
35
+ /** Dashboard widgets, by widget type (`stat`). */
36
+ widgets?: Record<string, PluginComponent>;
37
+ /** Layout node components, for `component` nodes of form layouts. */
38
+ components?: Record<string, PluginComponent>;
39
+ /** UI messages, by locale. Messages of later plugins override earlier ones. */
40
+ locales?: Record<string, MessagesLoader>;
41
+ /** Themes, by id. */
42
+ themes?: Record<string, Theme>;
43
+ }
44
+ /** The kinds of components a plugin can provide. */
45
+ export type PluginComponentKind = 'fields' | 'displays' | 'listLayouts' | 'actions' | 'pages' | 'widgets' | 'components';
46
+ /** Everything the plugins provide, merged. A later plugin overrides an earlier one. */
47
+ export interface PluginRegistry {
48
+ /** The plugins, in order. */
49
+ plugins: readonly PanneauPlugin[];
50
+ fields: ReadonlyMap<string, PluginComponent>;
51
+ displays: ReadonlyMap<string, PluginComponent>;
52
+ listLayouts: ReadonlyMap<string, PluginComponent>;
53
+ actions: ReadonlyMap<string, PluginComponent>;
54
+ pages: ReadonlyMap<string, PluginComponent>;
55
+ widgets: ReadonlyMap<string, PluginComponent>;
56
+ components: ReadonlyMap<string, PluginComponent>;
57
+ /** Message loaders of each locale, in plugin order. */
58
+ locales: ReadonlyMap<string, readonly MessagesLoader[]>;
59
+ themes: ReadonlyMap<string, Theme>;
60
+ }
61
+ /** Declares a plugin. It returns the plugin as is, with its type checked. */
62
+ export declare function definePlugin(plugin: PanneauPlugin): PanneauPlugin;
63
+ /**
64
+ * Merges plugins into a registry. Later plugins override the components and themes of earlier
65
+ * ones, so a project can replace a built-in field. Plugin ids must be unique.
66
+ */
67
+ export declare function createPluginRegistry(plugins: readonly PanneauPlugin[]): PluginRegistry;
68
+ /**
69
+ * Loads the messages of a locale from every plugin that provides it, merged in plugin order. A
70
+ * regional locale (`fr-CA`) falls back to its language (`fr`) for missing messages.
71
+ */
72
+ export declare function loadMessages(registry: PluginRegistry, locale: string): Promise<Messages>;
73
+ //# sourceMappingURL=plugins.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plugins.d.ts","sourceRoot":"","sources":["../src/plugins.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,OAAO,CAAC;AAE3C;;;GAGG;AAEH,MAAM,MAAM,eAAe,GAAG,aAAa,CAAC,GAAG,CAAC,CAAC;AAEjD,oDAAoD;AACpD,MAAM,MAAM,QAAQ,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;AAE9C,kFAAkF;AAClF,MAAM,MAAM,cAAc,GAAG,QAAQ,GAAG,CAAC,MAAM,OAAO,CAAC,QAAQ,GAAG;IAAE,OAAO,EAAE,QAAQ,CAAA;CAAE,CAAC,CAAC,CAAC;AAE1F,gGAAgG;AAChG,MAAM,WAAW,KAAK;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,2GAA2G;IAC3G,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC/B,6CAA6C;IAC7C,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACjC;AAED,0FAA0F;AAC1F,MAAM,WAAW,aAAa;IAC1B,+BAA+B;IAC/B,EAAE,EAAE,MAAM,CAAC;IACX,6DAA6D;IAC7D,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,eAAe,CAAC,CAAC;IACzC,kFAAkF;IAClF,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,eAAe,CAAC,CAAC;IAC3C,8CAA8C;IAC9C,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,eAAe,CAAC,CAAC;IAC9C,iEAAiE;IACjE,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,eAAe,CAAC,CAAC;IAC1C,8CAA8C;IAC9C,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,eAAe,CAAC,CAAC;IACxC,kDAAkD;IAClD,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,eAAe,CAAC,CAAC;IAC1C,qEAAqE;IACrE,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,eAAe,CAAC,CAAC;IAC7C,+EAA+E;IAC/E,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,cAAc,CAAC,CAAC;IACzC,qBAAqB;IACrB,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;CAClC;AAED,oDAAoD;AACpD,MAAM,MAAM,mBAAmB,GAC3B,QAAQ,GAAG,UAAU,GAAG,aAAa,GAAG,SAAS,GAAG,OAAO,GAAG,SAAS,GAAG,YAAY,CAAC;AAY3F,uFAAuF;AACvF,MAAM,WAAW,cAAc;IAC3B,6BAA6B;IAC7B,OAAO,EAAE,SAAS,aAAa,EAAE,CAAC;IAClC,MAAM,EAAE,WAAW,CAAC,MAAM,EAAE,eAAe,CAAC,CAAC;IAC7C,QAAQ,EAAE,WAAW,CAAC,MAAM,EAAE,eAAe,CAAC,CAAC;IAC/C,WAAW,EAAE,WAAW,CAAC,MAAM,EAAE,eAAe,CAAC,CAAC;IAClD,OAAO,EAAE,WAAW,CAAC,MAAM,EAAE,eAAe,CAAC,CAAC;IAC9C,KAAK,EAAE,WAAW,CAAC,MAAM,EAAE,eAAe,CAAC,CAAC;IAC5C,OAAO,EAAE,WAAW,CAAC,MAAM,EAAE,eAAe,CAAC,CAAC;IAC9C,UAAU,EAAE,WAAW,CAAC,MAAM,EAAE,eAAe,CAAC,CAAC;IACjD,uDAAuD;IACvD,OAAO,EAAE,WAAW,CAAC,MAAM,EAAE,SAAS,cAAc,EAAE,CAAC,CAAC;IACxD,MAAM,EAAE,WAAW,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;CACtC;AAED,6EAA6E;AAC7E,wBAAgB,YAAY,CAAC,MAAM,EAAE,aAAa,GAAG,aAAa,CAEjE;AAED;;;GAGG;AACH,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,SAAS,aAAa,EAAE,GAAG,cAAc,CA0BtF;AAED;;;GAGG;AACH,wBAAsB,YAAY,CAAC,QAAQ,EAAE,cAAc,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,QAAQ,CAAC,CAkB9F"}
@@ -0,0 +1,54 @@
1
+ //#region src/plugins.ts
2
+ var COMPONENT_KINDS = [
3
+ "fields",
4
+ "displays",
5
+ "listLayouts",
6
+ "actions",
7
+ "pages",
8
+ "widgets",
9
+ "components"
10
+ ];
11
+ /** Declares a plugin. It returns the plugin as is, with its type checked. */
12
+ function definePlugin(plugin) {
13
+ return plugin;
14
+ }
15
+ /**
16
+ * Merges plugins into a registry. Later plugins override the components and themes of earlier
17
+ * ones, so a project can replace a built-in field. Plugin ids must be unique.
18
+ */
19
+ function createPluginRegistry(plugins) {
20
+ const ids = /* @__PURE__ */ new Set();
21
+ plugins.forEach((plugin) => {
22
+ if (ids.has(plugin.id)) throw new Error(`Two plugins have the id \`${plugin.id}\`.`);
23
+ ids.add(plugin.id);
24
+ });
25
+ const components = Object.fromEntries(COMPONENT_KINDS.map((kind) => [kind, new Map(plugins.flatMap((plugin) => Object.entries(plugin[kind] ?? {})))]));
26
+ const locales = /* @__PURE__ */ new Map();
27
+ plugins.forEach((plugin) => Object.entries(plugin.locales ?? {}).forEach(([locale, loader]) => {
28
+ locales.set(locale, [...locales.get(locale) ?? [], loader]);
29
+ }));
30
+ return {
31
+ plugins: [...plugins],
32
+ ...components,
33
+ locales,
34
+ themes: new Map(plugins.flatMap((plugin) => Object.entries(plugin.themes ?? {})))
35
+ };
36
+ }
37
+ /**
38
+ * Loads the messages of a locale from every plugin that provides it, merged in plugin order. A
39
+ * regional locale (`fr-CA`) falls back to its language (`fr`) for missing messages.
40
+ */
41
+ async function loadMessages(registry, locale) {
42
+ const language = locale.split("-")[0] ?? locale;
43
+ const loaders = [...language !== locale ? registry.locales.get(language) ?? [] : [], ...registry.locales.get(locale) ?? []];
44
+ const loaded = await Promise.all(loaders.map(async (loader) => {
45
+ if (typeof loader !== "function") return loader;
46
+ const result = await loader();
47
+ return "default" in result && typeof result.default === "object" ? result.default : result;
48
+ }));
49
+ return Object.assign({}, ...loaded);
50
+ }
51
+ //#endregion
52
+ export { createPluginRegistry, definePlugin, loadMessages };
53
+
54
+ //# sourceMappingURL=plugins.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plugins.js","names":[],"sources":["../src/plugins.ts"],"sourcesContent":["import type { ComponentType } from 'react';\n\n/**\n * A component provided by a plugin. Its props are defined by the package that renders it (fields,\n * lists, app), so the registry stores it as given.\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any -- see above\nexport type PluginComponent = ComponentType<any>;\n\n/** UI messages of a locale, keyed by message id. */\nexport type Messages = Record<string, string>;\n\n/** Messages, or a function that loads them (`() => import('./lang/fr.json')`). */\nexport type MessagesLoader = Messages | (() => Promise<Messages | { default: Messages }>);\n\n/** A theme: design tokens (CSS variables without `--`), with optional dark scheme overrides. */\nexport interface Theme {\n label?: string;\n /** Tokens of the light scheme, or of both schemes without `dark`: `{ 'panneau-color-accent': '#05f' }`. */\n tokens: Record<string, string>;\n /** Tokens that change in the dark scheme. */\n dark?: Record<string, string>;\n}\n\n/** What a plugin adds to Panneau. Every part is keyed by the name the definition uses. */\nexport interface PanneauPlugin {\n /** Unique id of the plugin. */\n id: string;\n /** Field components, by field type (`text`, `page-slug`). */\n fields?: Record<string, PluginComponent>;\n /** Components that display a value in lists, by display (`date`, `page-slug`). */\n displays?: Record<string, PluginComponent>;\n /** List layouts, by id (`table`, `cards`). */\n listLayouts?: Record<string, PluginComponent>;\n /** Handlers of `client` actions, by the action's `component`. */\n actions?: Record<string, PluginComponent>;\n /** Pages, by component name (`dashboard`). */\n pages?: Record<string, PluginComponent>;\n /** Dashboard widgets, by widget type (`stat`). */\n widgets?: Record<string, PluginComponent>;\n /** Layout node components, for `component` nodes of form layouts. */\n components?: Record<string, PluginComponent>;\n /** UI messages, by locale. Messages of later plugins override earlier ones. */\n locales?: Record<string, MessagesLoader>;\n /** Themes, by id. */\n themes?: Record<string, Theme>;\n}\n\n/** The kinds of components a plugin can provide. */\nexport type PluginComponentKind =\n 'fields' | 'displays' | 'listLayouts' | 'actions' | 'pages' | 'widgets' | 'components';\n\nconst COMPONENT_KINDS: PluginComponentKind[] = [\n 'fields',\n 'displays',\n 'listLayouts',\n 'actions',\n 'pages',\n 'widgets',\n 'components',\n];\n\n/** Everything the plugins provide, merged. A later plugin overrides an earlier one. */\nexport interface PluginRegistry {\n /** The plugins, in order. */\n plugins: readonly PanneauPlugin[];\n fields: ReadonlyMap<string, PluginComponent>;\n displays: ReadonlyMap<string, PluginComponent>;\n listLayouts: ReadonlyMap<string, PluginComponent>;\n actions: ReadonlyMap<string, PluginComponent>;\n pages: ReadonlyMap<string, PluginComponent>;\n widgets: ReadonlyMap<string, PluginComponent>;\n components: ReadonlyMap<string, PluginComponent>;\n /** Message loaders of each locale, in plugin order. */\n locales: ReadonlyMap<string, readonly MessagesLoader[]>;\n themes: ReadonlyMap<string, Theme>;\n}\n\n/** Declares a plugin. It returns the plugin as is, with its type checked. */\nexport function definePlugin(plugin: PanneauPlugin): PanneauPlugin {\n return plugin;\n}\n\n/**\n * Merges plugins into a registry. Later plugins override the components and themes of earlier\n * ones, so a project can replace a built-in field. Plugin ids must be unique.\n */\nexport function createPluginRegistry(plugins: readonly PanneauPlugin[]): PluginRegistry {\n const ids = new Set<string>();\n plugins.forEach((plugin) => {\n if (ids.has(plugin.id)) {\n throw new Error(`Two plugins have the id \\`${plugin.id}\\`.`);\n }\n ids.add(plugin.id);\n });\n const components = Object.fromEntries(\n COMPONENT_KINDS.map((kind) => [\n kind,\n new Map(plugins.flatMap((plugin) => Object.entries(plugin[kind] ?? {}))),\n ]),\n ) as Record<PluginComponentKind, Map<string, PluginComponent>>;\n const locales = new Map<string, MessagesLoader[]>();\n plugins.forEach((plugin) =>\n Object.entries(plugin.locales ?? {}).forEach(([locale, loader]) => {\n locales.set(locale, [...(locales.get(locale) ?? []), loader]);\n }),\n );\n return {\n plugins: [...plugins],\n ...components,\n locales,\n themes: new Map(plugins.flatMap((plugin) => Object.entries(plugin.themes ?? {}))),\n };\n}\n\n/**\n * Loads the messages of a locale from every plugin that provides it, merged in plugin order. A\n * regional locale (`fr-CA`) falls back to its language (`fr`) for missing messages.\n */\nexport async function loadMessages(registry: PluginRegistry, locale: string): Promise<Messages> {\n const language = locale.split('-')[0] ?? locale;\n const loaders = [\n ...(language !== locale ? (registry.locales.get(language) ?? []) : []),\n ...(registry.locales.get(locale) ?? []),\n ];\n const loaded = await Promise.all(\n loaders.map(async (loader) => {\n if (typeof loader !== 'function') {\n return loader;\n }\n const result = await loader();\n return 'default' in result && typeof result.default === 'object'\n ? (result.default as Messages)\n : (result as Messages);\n }),\n );\n return Object.assign({}, ...loaded) as Messages;\n}\n"],"mappings":";AAoDA,IAAM,kBAAyC;CAC3C;CACA;CACA;CACA;CACA;CACA;CACA;AAAY;;AAoBhB,SAAgB,aAAa,QAAsC;CAC/D,OAAO;AACX;;;;;AAMA,SAAgB,qBAAqB,SAAmD;CACpF,MAAM,sBAAM,IAAI,IAAY;CAC5B,QAAQ,SAAS,WAAW;EACxB,IAAI,IAAI,IAAI,OAAO,EAAE,GACjB,MAAM,IAAI,MAAM,6BAA6B,OAAO,GAAE,IAAK;EAE/D,IAAI,IAAI,OAAO,EAAE;CACrB,CAAC;CACD,MAAM,aAAa,OAAO,YACtB,gBAAgB,KAAK,SAAS,CAC1B,MACA,IAAI,IAAI,QAAQ,SAAS,WAAW,OAAO,QAAQ,OAAO,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,CAC3E,CACL;CACA,MAAM,0BAAU,IAAI,IAA8B;CAClD,QAAQ,SAAS,WACb,OAAO,QAAQ,OAAO,WAAW,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,QAAQ,YAAY;EAC/D,QAAQ,IAAI,QAAQ,CAAC,GAAI,QAAQ,IAAI,MAAM,KAAK,CAAA,GAAK,MAAM,CAAC;CAChE,CAAC,CACL;CACA,OAAO;EACH,SAAS,CAAC,GAAG,OAAO;EACpB,GAAG;EACH;EACA,QAAQ,IAAI,IAAI,QAAQ,SAAS,WAAW,OAAO,QAAQ,OAAO,UAAU,CAAC,CAAC,CAAC,CAAC;CACpF;AACJ;;;;;AAMA,eAAsB,aAAa,UAA0B,QAAmC;CAC5F,MAAM,WAAW,OAAO,MAAM,GAAG,CAAC,CAAC,MAAM;CACzC,MAAM,UAAU,CACZ,GAAI,aAAa,SAAU,SAAS,QAAQ,IAAI,QAAQ,KAAK,CAAA,IAAM,CAAA,GACnE,GAAI,SAAS,QAAQ,IAAI,MAAM,KAAK,CAAA,CAAG;CAE3C,MAAM,SAAS,MAAM,QAAQ,IACzB,QAAQ,IAAI,OAAO,WAAW;EAC1B,IAAI,OAAO,WAAW,YAClB,OAAO;EAEX,MAAM,SAAS,MAAM,OAAO;EAC5B,OAAO,aAAa,UAAU,OAAO,OAAO,YAAY,WACjD,OAAO,UACP;CACX,CAAC,CACL;CACA,OAAO,OAAO,OAAO,CAAC,GAAG,GAAG,MAAM;AACtC"}
@@ -0,0 +1,38 @@
1
+ import { type ReactNode } from 'react';
2
+ import type { Definition } from '@panneau/protocol';
3
+ import { type ResolveFormOptions } from '../definition/form';
4
+ import { type ResolvedResource } from '../definition/resource';
5
+ import type { Router } from '../list/router';
6
+ import { type PanneauPlugin, type PluginRegistry } from '../plugins';
7
+ /** What `PanneauProvider` gives to the hooks below it. */
8
+ export interface PanneauContextValue {
9
+ plugins: PluginRegistry;
10
+ /** The plugins as passed, including those of parent providers. */
11
+ pluginList: readonly PanneauPlugin[];
12
+ definition: Definition | null;
13
+ router: Router | null;
14
+ }
15
+ export interface PanneauProviderProps {
16
+ /** Plugins, added after those of a parent provider (so they override them). */
17
+ plugins?: readonly PanneauPlugin[];
18
+ /** The definition, for `useResource()`. Defaults to the parent provider's. */
19
+ definition?: Definition | null;
20
+ /** The router that lists keep their query in. Defaults to the parent provider's. */
21
+ router?: Router | null;
22
+ children?: ReactNode;
23
+ }
24
+ /**
25
+ * Provides plugins, the definition and the router to the hooks of `@panneau/core`. Providers can
26
+ * be nested: a nested provider adds its plugins to those of its parent.
27
+ */
28
+ export declare function PanneauProvider({ plugins, definition, router, children, }: PanneauProviderProps): import("react").JSX.Element;
29
+ /** The context of the closest `PanneauProvider`, or an empty one without a provider. */
30
+ export declare function usePanneau(): PanneauContextValue;
31
+ /** The plugins of the closest `PanneauProvider`, merged. */
32
+ export declare function usePlugins(): PluginRegistry;
33
+ /**
34
+ * A resource of the definition, with its form, list and abilities resolved. Throws when there is
35
+ * no definition or no such resource.
36
+ */
37
+ export declare function useResource(id: string, formOptions?: ResolveFormOptions): ResolvedResource;
38
+ //# sourceMappingURL=provider.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"provider.d.ts","sourceRoot":"","sources":["../../src/react/provider.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,SAAS,EAAsB,MAAM,OAAO,CAAC;AAE3D,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAEpD,OAAO,EAAE,KAAK,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AAC7D,OAAO,EAAE,KAAK,gBAAgB,EAAiC,MAAM,wBAAwB,CAAC;AAC9F,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,gBAAgB,CAAC;AAC7C,OAAO,EAAE,KAAK,aAAa,EAAE,KAAK,cAAc,EAAwB,MAAM,YAAY,CAAC;AAE3F,0DAA0D;AAC1D,MAAM,WAAW,mBAAmB;IAChC,OAAO,EAAE,cAAc,CAAC;IACxB,kEAAkE;IAClE,UAAU,EAAE,SAAS,aAAa,EAAE,CAAC;IACrC,UAAU,EAAE,UAAU,GAAG,IAAI,CAAC;IAC9B,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;CACzB;AAID,MAAM,WAAW,oBAAoB;IACjC,+EAA+E;IAC/E,OAAO,CAAC,EAAE,SAAS,aAAa,EAAE,CAAC;IACnC,8EAA8E;IAC9E,UAAU,CAAC,EAAE,UAAU,GAAG,IAAI,CAAC;IAC/B,oFAAoF;IACpF,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,QAAQ,CAAC,EAAE,SAAS,CAAC;CACxB;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAAC,EAC5B,OAAY,EACZ,UAAU,EACV,MAAM,EACN,QAAQ,GACX,EAAE,oBAAoB,+BAUtB;AASD,wFAAwF;AACxF,wBAAgB,UAAU,IAAI,mBAAmB,CAEhD;AAED,4DAA4D;AAC5D,wBAAgB,UAAU,IAAI,cAAc,CAE3C;AAED;;;GAGG;AACH,wBAAgB,WAAW,CAAC,EAAE,EAAE,MAAM,EAAE,WAAW,CAAC,EAAE,kBAAkB,GAAG,gBAAgB,CAU1F"}
@@ -0,0 +1,63 @@
1
+ import { findResource, resolveResource } from "../definition/resource.js";
2
+ import { createPluginRegistry } from "../plugins.js";
3
+ import { c } from "react/compiler-runtime";
4
+ import { createContext, use } from "react";
5
+ import { jsx } from "react/jsx-runtime";
6
+ //#region src/react/provider.tsx
7
+ var PanneauContext = createContext(null);
8
+ /**
9
+ * Provides plugins, the definition and the router to the hooks of `@panneau/core`. Providers can
10
+ * be nested: a nested provider adds its plugins to those of its parent.
11
+ */
12
+ function PanneauProvider({ plugins = [], definition, router, children }) {
13
+ const parent = use(PanneauContext);
14
+ const pluginList = [...parent?.pluginList ?? [], ...plugins];
15
+ const value = {
16
+ plugins: createPluginRegistry(pluginList),
17
+ pluginList,
18
+ definition: definition === void 0 ? parent?.definition ?? null : definition,
19
+ router: router === void 0 ? parent?.router ?? null : router
20
+ };
21
+ return /* @__PURE__ */ jsx(PanneauContext, {
22
+ value,
23
+ children
24
+ });
25
+ }
26
+ var EMPTY = {
27
+ plugins: createPluginRegistry([]),
28
+ pluginList: [],
29
+ definition: null,
30
+ router: null
31
+ };
32
+ /** The context of the closest `PanneauProvider`, or an empty one without a provider. */
33
+ function usePanneau() {
34
+ return use(PanneauContext) ?? EMPTY;
35
+ }
36
+ /** The plugins of the closest `PanneauProvider`, merged. */
37
+ function usePlugins() {
38
+ return usePanneau().plugins;
39
+ }
40
+ /**
41
+ * A resource of the definition, with its form, list and abilities resolved. Throws when there is
42
+ * no definition or no such resource.
43
+ */
44
+ function useResource(id, formOptions) {
45
+ const $ = c(4);
46
+ const { definition } = usePanneau();
47
+ if (definition === null) throw new Error("useResource() needs a definition: pass one to <PanneauProvider>.");
48
+ let t0;
49
+ if ($[0] !== definition || $[1] !== formOptions || $[2] !== id) {
50
+ const resource = findResource(definition, id);
51
+ if (resource === void 0) throw new Error(`The definition has no resource \`${id}\`.`);
52
+ t0 = resolveResource(resource, formOptions);
53
+ $[0] = definition;
54
+ $[1] = formOptions;
55
+ $[2] = id;
56
+ $[3] = t0;
57
+ } else t0 = $[3];
58
+ return t0;
59
+ }
60
+ //#endregion
61
+ export { PanneauProvider, usePanneau, usePlugins, useResource };
62
+
63
+ //# sourceMappingURL=provider.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"provider.js","names":["_c"],"sources":["../../src/react/provider.tsx"],"sourcesContent":["import { type ReactNode, createContext, use } from 'react';\n\nimport type { Definition } from '@panneau/protocol';\n\nimport { type ResolveFormOptions } from '../definition/form';\nimport { type ResolvedResource, findResource, resolveResource } from '../definition/resource';\nimport type { Router } from '../list/router';\nimport { type PanneauPlugin, type PluginRegistry, createPluginRegistry } from '../plugins';\n\n/** What `PanneauProvider` gives to the hooks below it. */\nexport interface PanneauContextValue {\n plugins: PluginRegistry;\n /** The plugins as passed, including those of parent providers. */\n pluginList: readonly PanneauPlugin[];\n definition: Definition | null;\n router: Router | null;\n}\n\nconst PanneauContext = createContext<PanneauContextValue | null>(null);\n\nexport interface PanneauProviderProps {\n /** Plugins, added after those of a parent provider (so they override them). */\n plugins?: readonly PanneauPlugin[];\n /** The definition, for `useResource()`. Defaults to the parent provider's. */\n definition?: Definition | null;\n /** The router that lists keep their query in. Defaults to the parent provider's. */\n router?: Router | null;\n children?: ReactNode;\n}\n\n/**\n * Provides plugins, the definition and the router to the hooks of `@panneau/core`. Providers can\n * be nested: a nested provider adds its plugins to those of its parent.\n */\nexport function PanneauProvider({\n plugins = [],\n definition,\n router,\n children,\n}: PanneauProviderProps) {\n const parent = use(PanneauContext);\n const pluginList = [...(parent?.pluginList ?? []), ...plugins];\n const value: PanneauContextValue = {\n plugins: createPluginRegistry(pluginList),\n pluginList,\n definition: definition === undefined ? (parent?.definition ?? null) : definition,\n router: router === undefined ? (parent?.router ?? null) : router,\n };\n return <PanneauContext value={value}>{children}</PanneauContext>;\n}\n\nconst EMPTY: PanneauContextValue = {\n plugins: createPluginRegistry([]),\n pluginList: [],\n definition: null,\n router: null,\n};\n\n/** The context of the closest `PanneauProvider`, or an empty one without a provider. */\nexport function usePanneau(): PanneauContextValue {\n return use(PanneauContext) ?? EMPTY;\n}\n\n/** The plugins of the closest `PanneauProvider`, merged. */\nexport function usePlugins(): PluginRegistry {\n return usePanneau().plugins;\n}\n\n/**\n * A resource of the definition, with its form, list and abilities resolved. Throws when there is\n * no definition or no such resource.\n */\nexport function useResource(id: string, formOptions?: ResolveFormOptions): ResolvedResource {\n const { definition } = usePanneau();\n if (definition === null) {\n throw new Error('useResource() needs a definition: pass one to <PanneauProvider>.');\n }\n const resource = findResource(definition, id);\n if (resource === undefined) {\n throw new Error(`The definition has no resource \\`${id}\\`.`);\n }\n return resolveResource(resource, formOptions);\n}\n"],"mappings":";;;;;;AAkBA,IAAM,iBAAiB,cAA0C,IAAI;;;;;AAgBrE,SAAgB,gBAAgB,EAC5B,UAAU,CAAA,GACV,YACA,QACA,YACqB;CACrB,MAAM,SAAS,IAAI,cAAc;CACjC,MAAM,aAAa,CAAC,GAAI,QAAQ,cAAc,CAAA,GAAK,GAAG,OAAO;CAC7D,MAAM,QAA6B;EAC/B,SAAS,qBAAqB,UAAU;EACxC;EACA,YAAY,eAAe,KAAA,IAAa,QAAQ,cAAc,OAAQ;EACtE,QAAQ,WAAW,KAAA,IAAa,QAAQ,UAAU,OAAQ;CAC9D;CACA,OAAO,oBAAC,gBAAD;EAAuB;EAAQ;CAAyB,CAAA;AACnE;AAEA,IAAM,QAA6B;CAC/B,SAAS,qBAAqB,CAAA,CAAE;CAChC,YAAY,CAAA;CACZ,YAAY;CACZ,QAAQ;AACZ;;AAGA,SAAgB,aAAkC;CAC9C,OAAO,IAAI,cAAc,KAAK;AAClC;;AAGA,SAAO,aAAA;CAAA,OACI,WAAW,CAAC,CAAA;AAAQ;;;;;AAO/B,SAAO,YAAA,IAAA,aAAA;CAAA,MAAA,IAAAA,EAAA,CAAA;CACH,MAAA,EAAA,eAAuB,WAAW;CAClC,IAAI,eAAe,MACf,MAAM,IAAI,MAAM,kEAAkE;CACrF,IAAA;CAAA,IAAA,EAAA,OAAA,cAAA,EAAA,OAAA,eAAA,EAAA,OAAA,IAAA;EACD,MAAA,WAAiB,aAAa,YAAY,EAAE;EAC5C,IAAI,aAAa,KAAA,GACb,MAAM,IAAI,MAAM,oCAAoC,GAAE,IAAK;EAExD,KAAA,gBAAgB,UAAU,WAAW;EAAC,EAAA,KAAA;EAAA,EAAA,KAAA;EAAA,EAAA,KAAA;EAAA,EAAA,KAAA;CAAA,OAAA,KAAA,EAAA;CAAA,OAAtC;AAAsC"}
@@ -0,0 +1,26 @@
1
+ import { type Form, type FormOptions, type FormState } from '../form/form';
2
+ export interface UseFormResult extends FormState {
3
+ /** The form, to change values, submit, reset or resolve a conflict. */
4
+ form: Form;
5
+ }
6
+ /**
7
+ * Creates a form for the life of the component. `fields`, `locales`, `formatError` and `onSubmit`
8
+ * follow the latest render; initial values and the version don't: call `form.reset()`, or give
9
+ * the component a new `key`, to start from another item.
10
+ *
11
+ * Read the returned state with the selectors (`selectErrors(state, path)`,
12
+ * `selectVisible(state, field)`…), not with the form's reading methods, which go stale with the
13
+ * React Compiler (see {@link Form}).
14
+ */
15
+ export declare function useForm(options: FormOptions): UseFormResult;
16
+ export interface UseFormFieldResult {
17
+ value: unknown;
18
+ onChange: (value: unknown) => void;
19
+ onBlur: () => void;
20
+ /** Errors at the path, and at nested paths (`title.fr` for `title`). */
21
+ errors: string[];
22
+ touched: boolean;
23
+ }
24
+ /** Props for a controlled field at a dot path of a form. */
25
+ export declare function useFormField(form: Form, path: string): UseFormFieldResult;
26
+ //# sourceMappingURL=useForm.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useForm.d.ts","sourceRoot":"","sources":["../../src/react/useForm.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,KAAK,IAAI,EAAE,KAAK,WAAW,EAAE,KAAK,SAAS,EAAc,MAAM,cAAc,CAAC;AAGvF,MAAM,WAAW,aAAc,SAAQ,SAAS;IAC5C,uEAAuE;IACvE,IAAI,EAAE,IAAI,CAAC;CACd;AAED;;;;;;;;GAQG;AACH,wBAAgB,OAAO,CAAC,OAAO,EAAE,WAAW,GAAG,aAAa,CAQ3D;AAED,MAAM,WAAW,kBAAkB;IAC/B,KAAK,EAAE,OAAO,CAAC;IACf,QAAQ,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,CAAC;IACnC,MAAM,EAAE,MAAM,IAAI,CAAC;IACnB,wEAAwE;IACxE,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,OAAO,EAAE,OAAO,CAAC;CACpB;AAED,4DAA4D;AAC5D,wBAAgB,YAAY,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,GAAG,kBAAkB,CASzE"}