@cratis/scene.blueprint.default 4.39.0 → 4.40.0

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 (167) hide show
  1. package/ComponentName.ts +27 -0
  2. package/defaultBlueprintComponents.ts +10 -1
  3. package/dist/cjs/ComponentName.js +9 -0
  4. package/dist/cjs/ComponentName.js.map +1 -1
  5. package/dist/cjs/defaultBlueprintComponents.js +19 -1
  6. package/dist/cjs/defaultBlueprintComponents.js.map +1 -1
  7. package/dist/cjs/foundation/BulkBar.js +62 -0
  8. package/dist/cjs/foundation/BulkBar.js.map +1 -0
  9. package/dist/cjs/foundation/DataList.js +436 -0
  10. package/dist/cjs/foundation/DataList.js.map +1 -0
  11. package/dist/cjs/foundation/FormScreenBody.js +447 -0
  12. package/dist/cjs/foundation/FormScreenBody.js.map +1 -0
  13. package/dist/cjs/foundation/FoundationStates.js +2 -1
  14. package/dist/cjs/foundation/FoundationStates.js.map +1 -1
  15. package/dist/cjs/foundation/GlobalSearch.js +54 -0
  16. package/dist/cjs/foundation/GlobalSearch.js.map +1 -0
  17. package/dist/cjs/foundation/KanbanBoard.js +222 -0
  18. package/dist/cjs/foundation/KanbanBoard.js.map +1 -0
  19. package/dist/cjs/foundation/NotificationsButton.js +45 -0
  20. package/dist/cjs/foundation/NotificationsButton.js.map +1 -0
  21. package/dist/cjs/foundation/SubNavigation.js +72 -0
  22. package/dist/cjs/foundation/SubNavigation.js.map +1 -0
  23. package/dist/cjs/foundation/TabSet.js +101 -0
  24. package/dist/cjs/foundation/TabSet.js.map +1 -0
  25. package/dist/cjs/foundation/WizardProgress.js +185 -0
  26. package/dist/cjs/foundation/WizardProgress.js.map +1 -0
  27. package/dist/cjs/foundation/boardModel.js +67 -0
  28. package/dist/cjs/foundation/boardModel.js.map +1 -0
  29. package/dist/cjs/foundation/formModel.js +158 -0
  30. package/dist/cjs/foundation/formModel.js.map +1 -0
  31. package/dist/cjs/foundation/foundationComponentMetadata.js +54 -0
  32. package/dist/cjs/foundation/foundationComponentMetadata.js.map +1 -1
  33. package/dist/cjs/foundation/foundationDescriptors.js +400 -0
  34. package/dist/cjs/foundation/foundationDescriptors.js.map +1 -1
  35. package/dist/cjs/foundation/foundationSamples.js +303 -0
  36. package/dist/cjs/foundation/foundationSamples.js.map +1 -1
  37. package/dist/cjs/foundation/foundationStrings.js +72 -0
  38. package/dist/cjs/foundation/foundationStrings.js.map +1 -1
  39. package/dist/cjs/foundation/listModel.js +200 -0
  40. package/dist/cjs/foundation/listModel.js.map +1 -0
  41. package/dist/cjs/foundation/tabModel.js +41 -0
  42. package/dist/cjs/foundation/tabModel.js.map +1 -0
  43. package/dist/cjs/foundation/urlState.js +57 -0
  44. package/dist/cjs/foundation/urlState.js.map +1 -0
  45. package/dist/cjs/foundation/useCompactWidth.js +23 -0
  46. package/dist/cjs/foundation/useCompactWidth.js.map +1 -0
  47. package/dist/cjs/foundation/wizardModel.js +50 -0
  48. package/dist/cjs/foundation/wizardModel.js.map +1 -0
  49. package/dist/cjs/index.js +71 -0
  50. package/dist/cjs/index.js.map +1 -1
  51. package/dist/esm/ComponentName.d.ts +10 -1
  52. package/dist/esm/ComponentName.d.ts.map +1 -1
  53. package/dist/esm/ComponentName.js +9 -0
  54. package/dist/esm/ComponentName.js.map +1 -1
  55. package/dist/esm/defaultBlueprintComponents.d.ts.map +1 -1
  56. package/dist/esm/defaultBlueprintComponents.js +19 -1
  57. package/dist/esm/defaultBlueprintComponents.js.map +1 -1
  58. package/dist/esm/foundation/BulkBar.d.ts +4 -0
  59. package/dist/esm/foundation/BulkBar.d.ts.map +1 -0
  60. package/dist/esm/foundation/BulkBar.js +59 -0
  61. package/dist/esm/foundation/BulkBar.js.map +1 -0
  62. package/dist/esm/foundation/DataList.d.ts +3 -0
  63. package/dist/esm/foundation/DataList.d.ts.map +1 -0
  64. package/dist/esm/foundation/DataList.js +434 -0
  65. package/dist/esm/foundation/DataList.js.map +1 -0
  66. package/dist/esm/foundation/FormScreenBody.d.ts +3 -0
  67. package/dist/esm/foundation/FormScreenBody.d.ts.map +1 -0
  68. package/dist/esm/foundation/FormScreenBody.js +445 -0
  69. package/dist/esm/foundation/FormScreenBody.js.map +1 -0
  70. package/dist/esm/foundation/FoundationStates.js +2 -1
  71. package/dist/esm/foundation/FoundationStates.js.map +1 -1
  72. package/dist/esm/foundation/GlobalSearch.d.ts +3 -0
  73. package/dist/esm/foundation/GlobalSearch.d.ts.map +1 -0
  74. package/dist/esm/foundation/GlobalSearch.js +52 -0
  75. package/dist/esm/foundation/GlobalSearch.js.map +1 -0
  76. package/dist/esm/foundation/KanbanBoard.d.ts +3 -0
  77. package/dist/esm/foundation/KanbanBoard.d.ts.map +1 -0
  78. package/dist/esm/foundation/KanbanBoard.js +220 -0
  79. package/dist/esm/foundation/KanbanBoard.js.map +1 -0
  80. package/dist/esm/foundation/NotificationsButton.d.ts +3 -0
  81. package/dist/esm/foundation/NotificationsButton.d.ts.map +1 -0
  82. package/dist/esm/foundation/NotificationsButton.js +43 -0
  83. package/dist/esm/foundation/NotificationsButton.js.map +1 -0
  84. package/dist/esm/foundation/SubNavigation.d.ts +3 -0
  85. package/dist/esm/foundation/SubNavigation.d.ts.map +1 -0
  86. package/dist/esm/foundation/SubNavigation.js +70 -0
  87. package/dist/esm/foundation/SubNavigation.js.map +1 -0
  88. package/dist/esm/foundation/TabSet.d.ts +3 -0
  89. package/dist/esm/foundation/TabSet.d.ts.map +1 -0
  90. package/dist/esm/foundation/TabSet.js +99 -0
  91. package/dist/esm/foundation/TabSet.js.map +1 -0
  92. package/dist/esm/foundation/WizardProgress.d.ts +3 -0
  93. package/dist/esm/foundation/WizardProgress.d.ts.map +1 -0
  94. package/dist/esm/foundation/WizardProgress.js +183 -0
  95. package/dist/esm/foundation/WizardProgress.js.map +1 -0
  96. package/dist/esm/foundation/boardModel.d.ts +22 -0
  97. package/dist/esm/foundation/boardModel.d.ts.map +1 -0
  98. package/dist/esm/foundation/boardModel.js +61 -0
  99. package/dist/esm/foundation/boardModel.js.map +1 -0
  100. package/dist/esm/foundation/formModel.d.ts +48 -0
  101. package/dist/esm/foundation/formModel.d.ts.map +1 -0
  102. package/dist/esm/foundation/formModel.js +147 -0
  103. package/dist/esm/foundation/formModel.js.map +1 -0
  104. package/dist/esm/foundation/foundationComponentMetadata.d.ts.map +1 -1
  105. package/dist/esm/foundation/foundationComponentMetadata.js +54 -0
  106. package/dist/esm/foundation/foundationComponentMetadata.js.map +1 -1
  107. package/dist/esm/foundation/foundationDescriptors.d.ts.map +1 -1
  108. package/dist/esm/foundation/foundationDescriptors.js +400 -0
  109. package/dist/esm/foundation/foundationDescriptors.js.map +1 -1
  110. package/dist/esm/foundation/foundationSamples.d.ts.map +1 -1
  111. package/dist/esm/foundation/foundationSamples.js +303 -0
  112. package/dist/esm/foundation/foundationSamples.js.map +1 -1
  113. package/dist/esm/foundation/foundationStrings.d.ts +72 -0
  114. package/dist/esm/foundation/foundationStrings.d.ts.map +1 -1
  115. package/dist/esm/foundation/foundationStrings.js +72 -0
  116. package/dist/esm/foundation/foundationStrings.js.map +1 -1
  117. package/dist/esm/foundation/index.d.ts +16 -0
  118. package/dist/esm/foundation/index.d.ts.map +1 -1
  119. package/dist/esm/foundation/index.js +16 -0
  120. package/dist/esm/foundation/index.js.map +1 -1
  121. package/dist/esm/foundation/listModel.d.ts +49 -0
  122. package/dist/esm/foundation/listModel.d.ts.map +1 -0
  123. package/dist/esm/foundation/listModel.js +183 -0
  124. package/dist/esm/foundation/listModel.js.map +1 -0
  125. package/dist/esm/foundation/tabModel.d.ts +10 -0
  126. package/dist/esm/foundation/tabModel.d.ts.map +1 -0
  127. package/dist/esm/foundation/tabModel.js +37 -0
  128. package/dist/esm/foundation/tabModel.js.map +1 -0
  129. package/dist/esm/foundation/urlState.d.ts +4 -0
  130. package/dist/esm/foundation/urlState.d.ts.map +1 -0
  131. package/dist/esm/foundation/urlState.js +53 -0
  132. package/dist/esm/foundation/urlState.js.map +1 -0
  133. package/dist/esm/foundation/useCompactWidth.d.ts +3 -0
  134. package/dist/esm/foundation/useCompactWidth.d.ts.map +1 -0
  135. package/dist/esm/foundation/useCompactWidth.js +20 -0
  136. package/dist/esm/foundation/useCompactWidth.js.map +1 -0
  137. package/dist/esm/foundation/wizardModel.d.ts +16 -0
  138. package/dist/esm/foundation/wizardModel.d.ts.map +1 -0
  139. package/dist/esm/foundation/wizardModel.js +43 -0
  140. package/dist/esm/foundation/wizardModel.js.map +1 -0
  141. package/dist/esm/index.js +16 -0
  142. package/dist/esm/index.js.map +1 -1
  143. package/dist/esm/tsconfig.tsbuildinfo +1 -1
  144. package/foundation/BulkBar.tsx +38 -0
  145. package/foundation/DataList.tsx +208 -0
  146. package/foundation/FormScreenBody.tsx +196 -0
  147. package/foundation/FoundationStates.tsx +1 -1
  148. package/foundation/GlobalSearch.tsx +33 -0
  149. package/foundation/KanbanBoard.tsx +97 -0
  150. package/foundation/NotificationsButton.tsx +28 -0
  151. package/foundation/SubNavigation.tsx +49 -0
  152. package/foundation/TabSet.tsx +65 -0
  153. package/foundation/WizardProgress.tsx +109 -0
  154. package/foundation/boardModel.ts +64 -0
  155. package/foundation/formModel.ts +146 -0
  156. package/foundation/foundationComponentMetadata.ts +9 -0
  157. package/foundation/foundationDescriptors.ts +195 -0
  158. package/foundation/foundationSamples.ts +56 -0
  159. package/foundation/foundationStrings.ts +72 -0
  160. package/foundation/index.ts +16 -0
  161. package/foundation/listModel.ts +190 -0
  162. package/foundation/tabModel.ts +41 -0
  163. package/foundation/urlState.ts +52 -0
  164. package/foundation/useCompactWidth.ts +23 -0
  165. package/foundation/wizardModel.ts +54 -0
  166. package/package.json +8 -8
  167. package/shell/layout.css +513 -0
@@ -0,0 +1,183 @@
1
+ // Copyright (c) Cratis. All rights reserved.
2
+ // Licensed under the MIT license. See LICENSE file in the project root for full license information.
3
+ /** How a column's cell is drawn. */ var CellKind = /*#__PURE__*/ function(CellKind) {
4
+ CellKind["Text"] = "text";
5
+ CellKind["Number"] = "number";
6
+ CellKind["Currency"] = "currency";
7
+ CellKind["Date"] = "date";
8
+ CellKind["Status"] = "status";
9
+ return CellKind;
10
+ }({});
11
+ /** The ways the same records can be laid out. */ var ListVariant = /*#__PURE__*/ function(ListVariant) {
12
+ ListVariant["Table"] = "table";
13
+ ListVariant["Cards"] = "cards";
14
+ ListVariant["Grouped"] = "grouped";
15
+ return ListVariant;
16
+ }({});
17
+ const records = (value)=>Array.isArray(value) ? value.filter((entry)=>typeof entry === 'object' && entry !== null && !Array.isArray(entry)) : [];
18
+ /** Reads columns, dropping those without a field and repeated fields. */ function readListColumns(value) {
19
+ const seen = new Set();
20
+ return records(value).flatMap((entry)=>{
21
+ if (typeof entry.field !== 'string' || entry.field.length === 0 || seen.has(entry.field)) return [];
22
+ seen.add(entry.field);
23
+ const tones = typeof entry.tones === 'object' && entry.tones !== null && !Array.isArray(entry.tones) ? Object.fromEntries(Object.entries(entry.tones).filter((pair)=>typeof pair[1] === 'string')) : {};
24
+ return [
25
+ {
26
+ field: entry.field,
27
+ header: typeof entry.header === 'string' && entry.header.length > 0 ? entry.header : entry.field,
28
+ kind: Object.values(CellKind).includes(entry.kind) ? entry.kind : "text",
29
+ sortable: entry.sortable !== false,
30
+ tones,
31
+ ...typeof entry.currency === 'string' ? {
32
+ currency: entry.currency
33
+ } : {}
34
+ }
35
+ ];
36
+ });
37
+ }
38
+ /** Reads the variant, defaulting to the table. */ const readListVariant = (value)=>Object.values(ListVariant).includes(value) ? value : "table";
39
+ /** The value of a row's field as text for searching and display: a primitive as itself, anything else empty. */ const textOf = (row, field)=>{
40
+ const value = row[field];
41
+ return typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean' ? String(value) : '';
42
+ };
43
+ /** The id of a row: its `idField`, else its position, so a list without ids still selects and keys sensibly. */ const rowId = (row, index, idField)=>{
44
+ const value = row[idField];
45
+ return typeof value === 'string' || typeof value === 'number' ? String(value) : `row-${index}`;
46
+ };
47
+ /** Rows that contain the search text in any shown column, without regard to case or accents; empty search keeps every row. */ function searchRows(rows, columns, search) {
48
+ const needle = normalize(search);
49
+ if (needle.length === 0) return rows;
50
+ return rows.filter((row)=>columns.some((column)=>normalize(textOf(row, column.field)).includes(needle)));
51
+ }
52
+ /** Rows that satisfy a filter of conditions joined by AND or OR, in nested groups - the value the filter builder publishes. */ function filterRows(rows, filter) {
53
+ if (typeof filter !== 'object' || filter === null) return rows;
54
+ const group = filter;
55
+ const tests = [
56
+ ...(group.conditions ?? []).filter((condition)=>typeof condition?.field === 'string').filter(active).map((condition)=>(row)=>satisfies(row, condition)),
57
+ ...(group.groups ?? []).filter((nested)=>hasConditions(nested)).map((nested)=>(row)=>filterRows([
58
+ row
59
+ ], nested).length === 1)
60
+ ];
61
+ if (tests.length === 0) return rows;
62
+ return rows.filter((row)=>group.operator === 'or' ? tests.some((test)=>test(row)) : tests.every((test)=>test(row)));
63
+ }
64
+ const active = (condition)=>[
65
+ 'isEmpty',
66
+ 'true',
67
+ 'false'
68
+ ].includes(condition.operator) || (condition.value ?? '').trim().length > 0;
69
+ const hasConditions = (group)=>{
70
+ const candidate = group;
71
+ return (candidate?.conditions ?? []).some(active) || (candidate?.groups ?? []).some(hasConditions);
72
+ };
73
+ function satisfies(row, condition) {
74
+ const raw = row[condition.field];
75
+ const text = normalize(typeof raw === 'string' || typeof raw === 'number' || typeof raw === 'boolean' ? String(raw) : '');
76
+ const value = normalize(condition.value ?? '');
77
+ const number = Number(raw);
78
+ const wanted = Number(condition.value);
79
+ switch(condition.operator){
80
+ case 'contains':
81
+ return text.includes(value);
82
+ case 'equals':
83
+ case 'is':
84
+ case 'on':
85
+ return typeof raw === 'number' ? number === wanted : text === value;
86
+ case 'notEquals':
87
+ case 'isNot':
88
+ return typeof raw === 'number' ? number !== wanted : text !== value;
89
+ case 'startsWith':
90
+ return text.startsWith(value);
91
+ case 'isEmpty':
92
+ return text.length === 0;
93
+ case 'greater':
94
+ return number > wanted;
95
+ case 'greaterOrEqual':
96
+ return number >= wanted;
97
+ case 'less':
98
+ return number < wanted;
99
+ case 'lessOrEqual':
100
+ return number <= wanted;
101
+ case 'before':
102
+ return text < value;
103
+ case 'after':
104
+ return text > value;
105
+ case 'true':
106
+ return raw === true || text === 'true';
107
+ case 'false':
108
+ return raw === false || text === 'false';
109
+ default:
110
+ return true;
111
+ }
112
+ }
113
+ function normalize(text) {
114
+ return text.normalize('NFD').replace(/\p{Diacritic}/gu, '').toLocaleLowerCase();
115
+ }
116
+ /** Reads a sort from its address form, `field` or `-field` for descending; a field that is not a sortable column is no sort at all. */ function parseSort(text, columns) {
117
+ if (!text) return undefined;
118
+ const descending = text.startsWith('-');
119
+ const field = descending ? text.slice(1) : text;
120
+ return columns.some((column)=>column.field === field && column.sortable) ? {
121
+ field,
122
+ descending
123
+ } : undefined;
124
+ }
125
+ /** A sort in its address form. */ const formatSort = (sort)=>sort === undefined ? undefined : `${sort.descending ? '-' : ''}${sort.field}`;
126
+ /** The next sort when a header is pressed: ascending, then descending, then none. */ const nextSort = (current, field)=>current?.field !== field ? {
127
+ field,
128
+ descending: false
129
+ } : current.descending ? undefined : {
130
+ field,
131
+ descending: true
132
+ };
133
+ /** Rows in order of a column, numbers as numbers and text by the locale, with ties kept in their original order. */ function sortRows(rows, sort, locale) {
134
+ if (!sort) return rows;
135
+ const collator = new Intl.Collator(locale, {
136
+ numeric: true,
137
+ sensitivity: 'base'
138
+ });
139
+ return rows.map((row, index)=>({
140
+ row,
141
+ index
142
+ })).sort((left, right)=>{
143
+ const a = left.row[sort.field];
144
+ const b = right.row[sort.field];
145
+ const order = typeof a === 'number' && typeof b === 'number' ? a - b : collator.compare(textOf(left.row, sort.field), textOf(right.row, sort.field));
146
+ return order === 0 ? left.index - right.index : sort.descending ? -order : order;
147
+ }).map((entry)=>entry.row);
148
+ }
149
+ /** The slice of rows on a page (1-based), and how many pages there are; a page out of range is clamped rather than empty. */ function paginate(rows, page, pageSize) {
150
+ const size = pageSize > 0 ? Math.floor(pageSize) : rows.length || 1;
151
+ const pages = Math.max(1, Math.ceil(rows.length / size));
152
+ const clamped = Math.min(Math.max(1, Math.floor(Number.isFinite(page) ? page : 1)), pages);
153
+ return {
154
+ rows: rows.slice((clamped - 1) * size, clamped * size),
155
+ page: clamped,
156
+ pages
157
+ };
158
+ }
159
+ /** Groups rows by a field in the order the groups are first seen, with the sum of a numeric field as the subtotal when one is named. */ function groupRows(rows, groupBy, totalField) {
160
+ const groups = [];
161
+ for (const row of rows){
162
+ const key = textOf(row, groupBy);
163
+ const group = groups.find((candidate)=>candidate.key === key) ?? (groups.push({
164
+ key,
165
+ rows: []
166
+ }), groups[groups.length - 1]);
167
+ group.rows.push(row);
168
+ }
169
+ return totalField === undefined ? groups : groups.map((group)=>({
170
+ ...group,
171
+ subtotal: group.rows.reduce((sum, row)=>sum + (typeof row[totalField] === 'number' ? row[totalField] : 0), 0)
172
+ }));
173
+ }
174
+ /** Toggles an id in a selection that allows one or many. */ const toggleSelection = (selected, id, multiple)=>selected.includes(id) ? selected.filter((candidate)=>candidate !== id) : multiple ? [
175
+ ...selected,
176
+ id
177
+ ] : [
178
+ id
179
+ ];
180
+ /** Reads a selection from its address form: ids separated by commas. */ const parseSelection = (text)=>text ? text.split(',').filter((id)=>id.length > 0) : [];
181
+
182
+ export { CellKind, ListVariant, filterRows, formatSort, groupRows, nextSort, paginate, parseSelection, parseSort, readListColumns, readListVariant, rowId, searchRows, sortRows, textOf, toggleSelection };
183
+ //# sourceMappingURL=listModel.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"listModel.js","sources":["../../../foundation/listModel.ts"],"sourcesContent":["// Copyright (c) Cratis. All rights reserved.\n// Licensed under the MIT license. See LICENSE file in the project root for full license information.\n\n/** How a column's cell is drawn. */\nexport enum CellKind {\n Text = 'text',\n Number = 'number',\n Currency = 'currency',\n Date = 'date',\n Status = 'status',\n}\n\n/** One column of a list. */\nexport interface ListColumn {\n field: string;\n header: string;\n kind: CellKind;\n sortable: boolean;\n /** For a status column: the tone of each value (`success`, `warning`, `danger`, `info`, `neutral`). */\n tones: Record<string, string>;\n /** For a currency column: the ISO 4217 code. */\n currency?: string;\n}\n\n/** One record of a list, with the field values as the query returned them. */\nexport type ListRow = Record<string, unknown>;\n\n/** How the list is sorted. */\nexport interface ListSort {\n field: string;\n descending: boolean;\n}\n\n/** The ways the same records can be laid out. */\nexport enum ListVariant {\n Table = 'table',\n Cards = 'cards',\n Grouped = 'grouped',\n}\n\nconst records = (value: unknown): Record<string, unknown>[] =>\n Array.isArray(value) ? value.filter((entry): entry is Record<string, unknown> => typeof entry === 'object' && entry !== null && !Array.isArray(entry)) : [];\n\n/** Reads columns, dropping those without a field and repeated fields. */\nexport function readListColumns(value: unknown): ListColumn[] {\n const seen = new Set<string>();\n return records(value).flatMap(entry => {\n if (typeof entry.field !== 'string' || entry.field.length === 0 || seen.has(entry.field)) return [];\n seen.add(entry.field);\n const tones = typeof entry.tones === 'object' && entry.tones !== null && !Array.isArray(entry.tones)\n ? Object.fromEntries(Object.entries(entry.tones as Record<string, unknown>).filter((pair): pair is [string, string] => typeof pair[1] === 'string')) : {};\n return [{\n field: entry.field,\n header: typeof entry.header === 'string' && entry.header.length > 0 ? entry.header : entry.field,\n kind: (Object.values(CellKind) as string[]).includes(entry.kind as string) ? (entry.kind as CellKind) : CellKind.Text,\n sortable: entry.sortable !== false,\n tones,\n ...(typeof entry.currency === 'string' ? { currency: entry.currency } : {}),\n }];\n });\n}\n\n/** Reads the variant, defaulting to the table. */\nexport const readListVariant = (value: unknown): ListVariant => ((Object.values(ListVariant) as string[]).includes(value as string) ? (value as ListVariant) : ListVariant.Table);\n\n/** The value of a row's field as text for searching and display: a primitive as itself, anything else empty. */\nexport const textOf = (row: ListRow, field: string): string => {\n const value = row[field];\n return typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean' ? String(value) : '';\n};\n\n/** The id of a row: its `idField`, else its position, so a list without ids still selects and keys sensibly. */\nexport const rowId = (row: ListRow, index: number, idField: string): string => {\n const value = row[idField];\n return typeof value === 'string' || typeof value === 'number' ? String(value) : `row-${index}`;\n};\n\n/** Rows that contain the search text in any shown column, without regard to case or accents; empty search keeps every row. */\nexport function searchRows(rows: ListRow[], columns: ListColumn[], search: string): ListRow[] {\n const needle = normalize(search);\n if (needle.length === 0) return rows;\n return rows.filter(row => columns.some(column => normalize(textOf(row, column.field)).includes(needle)));\n}\n\n/** Rows that satisfy a filter of conditions joined by AND or OR, in nested groups - the value the filter builder publishes. */\nexport function filterRows(rows: ListRow[], filter: unknown): ListRow[] {\n if (typeof filter !== 'object' || filter === null) return rows;\n const group = filter as { operator?: string; conditions?: { field: string; operator: string; value: string }[]; groups?: unknown[] };\n const tests = [\n ...(group.conditions ?? []).filter(condition => typeof condition?.field === 'string').filter(active).map(condition => (row: ListRow) => satisfies(row, condition)),\n ...(group.groups ?? []).filter(nested => hasConditions(nested)).map(nested => (row: ListRow) => filterRows([row], nested).length === 1),\n ];\n if (tests.length === 0) return rows;\n return rows.filter(row => (group.operator === 'or' ? tests.some(test => test(row)) : tests.every(test => test(row))));\n}\n\nconst active = (condition: { operator: string; value: string }) => ['isEmpty', 'true', 'false'].includes(condition.operator) || (condition.value ?? '').trim().length > 0;\nconst hasConditions = (group: unknown): boolean => {\n const candidate = group as { conditions?: { operator: string; value: string }[]; groups?: unknown[] } | null;\n return (candidate?.conditions ?? []).some(active) || (candidate?.groups ?? []).some(hasConditions);\n};\n\nfunction satisfies(row: ListRow, condition: { field: string; operator: string; value: string }): boolean {\n const raw = row[condition.field];\n const text = normalize(typeof raw === 'string' || typeof raw === 'number' || typeof raw === 'boolean' ? String(raw) : '');\n const value = normalize(condition.value ?? '');\n const number = Number(raw);\n const wanted = Number(condition.value);\n switch (condition.operator) {\n case 'contains': return text.includes(value);\n case 'equals': case 'is': case 'on': return typeof raw === 'number' ? number === wanted : text === value;\n case 'notEquals': case 'isNot': return typeof raw === 'number' ? number !== wanted : text !== value;\n case 'startsWith': return text.startsWith(value);\n case 'isEmpty': return text.length === 0;\n case 'greater': return number > wanted;\n case 'greaterOrEqual': return number >= wanted;\n case 'less': return number < wanted;\n case 'lessOrEqual': return number <= wanted;\n case 'before': return text < value;\n case 'after': return text > value;\n case 'true': return raw === true || text === 'true';\n case 'false': return raw === false || text === 'false';\n default: return true;\n }\n}\n\nfunction normalize(text: string): string {\n return text.normalize('NFD').replace(/\\p{Diacritic}/gu, '').toLocaleLowerCase();\n}\n\n/** Reads a sort from its address form, `field` or `-field` for descending; a field that is not a sortable column is no sort at all. */\nexport function parseSort(text: string | undefined, columns: ListColumn[]): ListSort | undefined {\n if (!text) return undefined;\n const descending = text.startsWith('-');\n const field = descending ? text.slice(1) : text;\n return columns.some(column => column.field === field && column.sortable) ? { field, descending } : undefined;\n}\n\n/** A sort in its address form. */\nexport const formatSort = (sort: ListSort | undefined): string | undefined => (sort === undefined ? undefined : `${sort.descending ? '-' : ''}${sort.field}`);\n\n/** The next sort when a header is pressed: ascending, then descending, then none. */\nexport const nextSort = (current: ListSort | undefined, field: string): ListSort | undefined =>\n current?.field !== field ? { field, descending: false } : current.descending ? undefined : { field, descending: true };\n\n/** Rows in order of a column, numbers as numbers and text by the locale, with ties kept in their original order. */\nexport function sortRows(rows: ListRow[], sort: ListSort | undefined, locale: string): ListRow[] {\n if (!sort) return rows;\n const collator = new Intl.Collator(locale, { numeric: true, sensitivity: 'base' });\n return rows.map((row, index) => ({ row, index })).sort((left, right) => {\n const a = left.row[sort.field];\n const b = right.row[sort.field];\n const order = typeof a === 'number' && typeof b === 'number' ? a - b : collator.compare(textOf(left.row, sort.field), textOf(right.row, sort.field));\n return (order === 0 ? left.index - right.index : sort.descending ? -order : order);\n }).map(entry => entry.row);\n}\n\n/** The slice of rows on a page (1-based), and how many pages there are; a page out of range is clamped rather than empty. */\nexport function paginate<T>(rows: T[], page: number, pageSize: number): { rows: T[]; page: number; pages: number } {\n const size = pageSize > 0 ? Math.floor(pageSize) : rows.length || 1;\n const pages = Math.max(1, Math.ceil(rows.length / size));\n const clamped = Math.min(Math.max(1, Math.floor(Number.isFinite(page) ? page : 1)), pages);\n return { rows: rows.slice((clamped - 1) * size, clamped * size), page: clamped, pages };\n}\n\n/** A group of rows with a subtotal, for the grouped variant. */\nexport interface ListGroup {\n key: string;\n rows: ListRow[];\n subtotal?: number;\n}\n\n/** Groups rows by a field in the order the groups are first seen, with the sum of a numeric field as the subtotal when one is named. */\nexport function groupRows(rows: ListRow[], groupBy: string, totalField?: string): ListGroup[] {\n const groups: ListGroup[] = [];\n for (const row of rows) {\n const key = textOf(row, groupBy);\n const group = groups.find(candidate => candidate.key === key) ?? (groups.push({ key, rows: [] }), groups[groups.length - 1]);\n group.rows.push(row);\n }\n\n return totalField === undefined ? groups : groups.map(group => ({ ...group, subtotal: group.rows.reduce((sum, row) => sum + (typeof row[totalField] === 'number' ? (row[totalField] as number) : 0), 0) }));\n}\n\n/** Toggles an id in a selection that allows one or many. */\nexport const toggleSelection = (selected: string[], id: string, multiple: boolean): string[] =>\n selected.includes(id) ? selected.filter(candidate => candidate !== id) : multiple ? [...selected, id] : [id];\n\n/** Reads a selection from its address form: ids separated by commas. */\nexport const parseSelection = (text: string | undefined): string[] => (text ? text.split(',').filter(id => id.length > 0) : []);\n"],"names":["CellKind","ListVariant","records","value","Array","isArray","filter","entry","readListColumns","seen","Set","flatMap","field","length","has","add","tones","Object","fromEntries","entries","pair","header","kind","values","includes","sortable","currency","readListVariant","textOf","row","String","rowId","index","idField","searchRows","rows","columns","search","needle","normalize","some","column","filterRows","group","tests","conditions","condition","active","map","satisfies","groups","nested","hasConditions","operator","test","every","trim","candidate","raw","text","number","Number","wanted","startsWith","replace","toLocaleLowerCase","parseSort","undefined","descending","slice","formatSort","sort","nextSort","current","sortRows","locale","collator","Intl","Collator","numeric","sensitivity","left","right","a","b","order","compare","paginate","page","pageSize","size","Math","floor","pages","max","ceil","clamped","min","isFinite","groupRows","groupBy","totalField","key","find","push","subtotal","reduce","sum","toggleSelection","selected","id","multiple","parseSelection","split"],"mappings":"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"}
@@ -0,0 +1,10 @@
1
+ export interface TabItem {
2
+ id: string;
3
+ label: string;
4
+ badge?: string;
5
+ disabled: boolean;
6
+ }
7
+ export declare function readTabs(value: unknown): TabItem[];
8
+ export declare function activeTab(tabs: TabItem[], requested: string | undefined): string | undefined;
9
+ export declare function stepTab(tabs: TabItem[], current: string | undefined, direction: 'next' | 'previous' | 'first' | 'last'): string | undefined;
10
+ //# sourceMappingURL=tabModel.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tabModel.d.ts","sourceRoot":"","sources":["../../../foundation/tabModel.ts"],"names":[],"mappings":"AAIA,MAAM,WAAW,OAAO;IACpB,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,OAAO,CAAC;CACrB;AAGD,wBAAgB,QAAQ,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,EAAE,CAWlD;AAGD,wBAAgB,SAAS,CAAC,IAAI,EAAE,OAAO,EAAE,EAAE,SAAS,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,GAAG,SAAS,CAG5F;AAGD,wBAAgB,OAAO,CAAC,IAAI,EAAE,OAAO,EAAE,EAAE,OAAO,EAAE,MAAM,GAAG,SAAS,EAAE,SAAS,EAAE,MAAM,GAAG,UAAU,GAAG,OAAO,GAAG,MAAM,GAAG,MAAM,GAAG,SAAS,CAQ3I"}
@@ -0,0 +1,37 @@
1
+ // Copyright (c) Cratis. All rights reserved.
2
+ // Licensed under the MIT license. See LICENSE file in the project root for full license information.
3
+ /** One tab or sub-section: an id the address and the slot content use, a label, and an optional count badge. */ /** Reads tabs out of a property value; entries without an id are dropped and a repeated id keeps its first. */ function readTabs(value) {
4
+ if (!Array.isArray(value)) return [];
5
+ const seen = new Set();
6
+ return value.flatMap((entry)=>{
7
+ if (typeof entry !== 'object' || entry === null || Array.isArray(entry)) return [];
8
+ const { id, label, badge, disabled } = entry;
9
+ if (typeof id !== 'string' || id.length === 0 || seen.has(id)) return [];
10
+ seen.add(id);
11
+ return [
12
+ {
13
+ id,
14
+ label: typeof label === 'string' && label.length > 0 ? label : id,
15
+ disabled: disabled === true,
16
+ ...typeof badge === 'string' || typeof badge === 'number' ? {
17
+ badge: String(badge)
18
+ } : {}
19
+ }
20
+ ];
21
+ });
22
+ }
23
+ /** The active tab: the requested one when it exists and is enabled, else the first enabled one. Deep links to a tab that was removed fall back instead of showing nothing. */ function activeTab(tabs, requested) {
24
+ const wanted = tabs.find((tab)=>tab.id === requested && !tab.disabled);
25
+ return (wanted ?? tabs.find((tab)=>!tab.disabled))?.id;
26
+ }
27
+ /** The enabled tab one step from the current one, wrapping, or the first/last for Home and End. */ function stepTab(tabs, current, direction) {
28
+ const enabled = tabs.filter((tab)=>!tab.disabled);
29
+ if (enabled.length === 0) return undefined;
30
+ if (direction === 'first') return enabled[0].id;
31
+ if (direction === 'last') return enabled[enabled.length - 1].id;
32
+ const index = Math.max(0, enabled.findIndex((tab)=>tab.id === current));
33
+ return enabled[(index + (direction === 'next' ? 1 : -1) + enabled.length) % enabled.length].id;
34
+ }
35
+
36
+ export { activeTab, readTabs, stepTab };
37
+ //# sourceMappingURL=tabModel.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tabModel.js","sources":["../../../foundation/tabModel.ts"],"sourcesContent":["// Copyright (c) Cratis. All rights reserved.\n// Licensed under the MIT license. See LICENSE file in the project root for full license information.\n\n/** One tab or sub-section: an id the address and the slot content use, a label, and an optional count badge. */\nexport interface TabItem {\n id: string;\n label: string;\n badge?: string;\n disabled: boolean;\n}\n\n/** Reads tabs out of a property value; entries without an id are dropped and a repeated id keeps its first. */\nexport function readTabs(value: unknown): TabItem[] {\n if (!Array.isArray(value)) return [];\n\n const seen = new Set<string>();\n return value.flatMap((entry): TabItem[] => {\n if (typeof entry !== 'object' || entry === null || Array.isArray(entry)) return [];\n const { id, label, badge, disabled } = entry as Record<string, unknown>;\n if (typeof id !== 'string' || id.length === 0 || seen.has(id)) return [];\n seen.add(id);\n return [{ id, label: typeof label === 'string' && label.length > 0 ? label : id, disabled: disabled === true, ...(typeof badge === 'string' || typeof badge === 'number' ? { badge: String(badge) } : {}) }];\n });\n}\n\n/** The active tab: the requested one when it exists and is enabled, else the first enabled one. Deep links to a tab that was removed fall back instead of showing nothing. */\nexport function activeTab(tabs: TabItem[], requested: string | undefined): string | undefined {\n const wanted = tabs.find(tab => tab.id === requested && !tab.disabled);\n return (wanted ?? tabs.find(tab => !tab.disabled))?.id;\n}\n\n/** The enabled tab one step from the current one, wrapping, or the first/last for Home and End. */\nexport function stepTab(tabs: TabItem[], current: string | undefined, direction: 'next' | 'previous' | 'first' | 'last'): string | undefined {\n const enabled = tabs.filter(tab => !tab.disabled);\n if (enabled.length === 0) return undefined;\n if (direction === 'first') return enabled[0].id;\n if (direction === 'last') return enabled[enabled.length - 1].id;\n\n const index = Math.max(0, enabled.findIndex(tab => tab.id === current));\n return enabled[(index + (direction === 'next' ? 1 : -1) + enabled.length) % enabled.length].id;\n}\n"],"names":["readTabs","value","Array","isArray","seen","Set","flatMap","entry","id","label","badge","disabled","length","has","add","String","activeTab","tabs","requested","wanted","find","tab","stepTab","current","direction","enabled","filter","undefined","index","Math","max","findIndex"],"mappings":"AAAA;AACA;AAEA,iOASO,SAASA,SAASC,KAAc,EAAA;AACnC,IAAA,IAAI,CAACC,KAAAA,CAAMC,OAAO,CAACF,KAAAA,CAAAA,EAAQ,OAAO,EAAE;AAEpC,IAAA,MAAMG,OAAO,IAAIC,GAAAA,EAAAA;IACjB,OAAOJ,KAAAA,CAAMK,OAAO,CAAC,CAACC,KAAAA,GAAAA;QAClB,IAAI,OAAOA,KAAAA,KAAU,QAAA,IAAYA,KAAAA,KAAU,IAAA,IAAQL,MAAMC,OAAO,CAACI,KAAAA,CAAAA,EAAQ,OAAO,EAAE;QAClF,MAAM,EAAEC,EAAE,EAAEC,KAAK,EAAEC,KAAK,EAAEC,QAAQ,EAAE,GAAGJ,KAAAA;AACvC,QAAA,IAAI,OAAOC,EAAAA,KAAO,QAAA,IAAYA,EAAAA,CAAGI,MAAM,KAAK,CAAA,IAAKR,IAAAA,CAAKS,GAAG,CAACL,EAAAA,CAAAA,EAAK,OAAO,EAAE;AACxEJ,QAAAA,IAAAA,CAAKU,GAAG,CAACN,EAAAA,CAAAA;QACT,OAAO;AAAC,YAAA;AAAEA,gBAAAA,EAAAA;AAAIC,gBAAAA,KAAAA,EAAO,OAAOA,KAAAA,KAAU,QAAA,IAAYA,MAAMG,MAAM,GAAG,IAAIH,KAAAA,GAAQD,EAAAA;AAAIG,gBAAAA,QAAAA,EAAUA,QAAAA,KAAa,IAAA;AAAM,gBAAA,GAAI,OAAOD,KAAAA,KAAU,QAAA,IAAY,OAAOA,UAAU,QAAA,GAAW;AAAEA,oBAAAA,KAAAA,EAAOK,MAAAA,CAAOL,KAAAA;AAAO,iBAAA,GAAI;AAAI;AAAE,SAAA;AAChN,IAAA,CAAA,CAAA;AACJ;AAEA,+KACO,SAASM,SAAAA,CAAUC,IAAe,EAAEC,SAA6B,EAAA;AACpE,IAAA,MAAMC,MAAAA,GAASF,IAAAA,CAAKG,IAAI,CAACC,CAAAA,GAAAA,GAAOA,GAAAA,CAAIb,EAAE,KAAKU,SAAAA,IAAa,CAACG,GAAAA,CAAIV,QAAQ,CAAA;IACrE,OAAQQ,CAAAA,MAAAA,IAAUF,IAAAA,CAAKG,IAAI,CAACC,CAAAA,GAAAA,GAAO,CAACA,GAAAA,CAAIV,QAAQ,CAAA,GAAIH,EAAAA;AACxD;AAEA,oGACO,SAASc,OAAAA,CAAQL,IAAe,EAAEM,OAA2B,EAAEC,SAAiD,EAAA;IACnH,MAAMC,OAAAA,GAAUR,KAAKS,MAAM,CAACL,CAAAA,GAAAA,GAAO,CAACA,IAAIV,QAAQ,CAAA;AAChD,IAAA,IAAIc,OAAAA,CAAQb,MAAM,KAAK,CAAA,EAAG,OAAOe,SAAAA;AACjC,IAAA,IAAIH,cAAc,OAAA,EAAS,OAAOC,OAAO,CAAC,CAAA,CAAE,CAACjB,EAAE;IAC/C,IAAIgB,SAAAA,KAAc,MAAA,EAAQ,OAAOC,OAAO,CAACA,QAAQb,MAAM,GAAG,CAAA,CAAE,CAACJ,EAAE;AAE/D,IAAA,MAAMoB,KAAAA,GAAQC,IAAAA,CAAKC,GAAG,CAAC,CAAA,EAAGL,OAAAA,CAAQM,SAAS,CAACV,CAAAA,GAAAA,GAAOA,GAAAA,CAAIb,EAAE,KAAKe,OAAAA,CAAAA,CAAAA;IAC9D,OAAOE,OAAO,CAAC,CAACG,SAASJ,SAAAA,KAAc,SAAS,CAAA,GAAI,EAAC,CAAA,GAAKC,OAAAA,CAAQb,MAAM,IAAIa,QAAQb,MAAM,CAAC,CAACJ,EAAE;AAClG;;"}
@@ -0,0 +1,4 @@
1
+ export declare function readUrlParameter(name: string): string | undefined;
2
+ export declare function writeUrlParameter(name: string, value: string | undefined): void;
3
+ export declare function useUrlState(key: string | undefined, fallback: string | undefined): [string | undefined, (value: string | undefined) => void];
4
+ //# sourceMappingURL=urlState.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"urlState.d.ts","sourceRoot":"","sources":["../../../foundation/urlState.ts"],"names":[],"mappings":"AAMA,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAIjE;AAGD,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,CAQ/E;AAcD,wBAAgB,WAAW,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,EAAE,QAAQ,EAAE,MAAM,GAAG,SAAS,GAAG,CAAC,MAAM,GAAG,SAAS,EAAE,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,KAAK,IAAI,CAAC,CAgB5I"}
@@ -0,0 +1,53 @@
1
+ import { useState, useEffect, useCallback } from 'react';
2
+
3
+ // Copyright (c) Cratis. All rights reserved.
4
+ // Licensed under the MIT license. See LICENSE file in the project root for full license information.
5
+ /** Reads one query-string parameter of the current address, or undefined when it is absent or there is no address (server rendering). */ function readUrlParameter(name) {
6
+ if (typeof window === 'undefined') return undefined;
7
+ const value = new URLSearchParams(window.location.search).get(name);
8
+ return value === null ? undefined : value;
9
+ }
10
+ /** Writes one query-string parameter to the address without adding a history entry; an undefined or empty value removes it. */ function writeUrlParameter(name, value) {
11
+ if (typeof window === 'undefined') return;
12
+ const parameters = new URLSearchParams(window.location.search);
13
+ if (value === undefined || value === '') parameters.delete(name);
14
+ else parameters.set(name, value);
15
+ const query = parameters.toString();
16
+ window.history.replaceState(window.history.state, '', `${window.location.pathname}${query.length > 0 ? `?${query}` : ''}${window.location.hash}`);
17
+ }
18
+ /**
19
+ * State that lives in the address bar: a list's search, sort, page and filters, a master/detail's selection, a module's active section.
20
+ *
21
+ * It starts from the address when the address has a value (so a shared link, a refresh and the back button restore the screen as it was),
22
+ * otherwise from the fallback the template supplies. Changing it rewrites the address in place and does not add a history entry per keystroke.
23
+ * Several components on one screen use distinct `key`s; a template names them (`q`, `sort`, `page`, `selected`, `section`). Without a
24
+ * `urlKey` the state is plain component state, which is what a gallery card and a design surface want.
25
+ *
26
+ * @param key The query-string parameter, or undefined to keep the state local.
27
+ * @param fallback The value used when the address has none.
28
+ * @returns The value and its setter.
29
+ */ function useUrlState(key, fallback) {
30
+ const [value, setValue] = useState(()=>key === undefined ? fallback : readUrlParameter(key) ?? fallback);
31
+ useEffect(()=>{
32
+ if (key === undefined) return;
33
+ const restore = ()=>setValue(readUrlParameter(key) ?? fallback);
34
+ window.addEventListener('popstate', restore);
35
+ return ()=>window.removeEventListener('popstate', restore);
36
+ }, [
37
+ key,
38
+ fallback
39
+ ]);
40
+ const update = useCallback((next)=>{
41
+ setValue(next);
42
+ if (key !== undefined) writeUrlParameter(key, next);
43
+ }, [
44
+ key
45
+ ]);
46
+ return [
47
+ value,
48
+ update
49
+ ];
50
+ }
51
+
52
+ export { readUrlParameter, useUrlState, writeUrlParameter };
53
+ //# sourceMappingURL=urlState.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"urlState.js","sources":["../../../foundation/urlState.ts"],"sourcesContent":["// Copyright (c) Cratis. All rights reserved.\n// Licensed under the MIT license. See LICENSE file in the project root for full license information.\n\nimport { useCallback, useEffect, useState } from 'react';\n\n/** Reads one query-string parameter of the current address, or undefined when it is absent or there is no address (server rendering). */\nexport function readUrlParameter(name: string): string | undefined {\n if (typeof window === 'undefined') return undefined;\n const value = new URLSearchParams(window.location.search).get(name);\n return value === null ? undefined : value;\n}\n\n/** Writes one query-string parameter to the address without adding a history entry; an undefined or empty value removes it. */\nexport function writeUrlParameter(name: string, value: string | undefined): void {\n if (typeof window === 'undefined') return;\n const parameters = new URLSearchParams(window.location.search);\n if (value === undefined || value === '') parameters.delete(name);\n else parameters.set(name, value);\n\n const query = parameters.toString();\n window.history.replaceState(window.history.state, '', `${window.location.pathname}${query.length > 0 ? `?${query}` : ''}${window.location.hash}`);\n}\n\n/**\n * State that lives in the address bar: a list's search, sort, page and filters, a master/detail's selection, a module's active section.\n *\n * It starts from the address when the address has a value (so a shared link, a refresh and the back button restore the screen as it was),\n * otherwise from the fallback the template supplies. Changing it rewrites the address in place and does not add a history entry per keystroke.\n * Several components on one screen use distinct `key`s; a template names them (`q`, `sort`, `page`, `selected`, `section`). Without a\n * `urlKey` the state is plain component state, which is what a gallery card and a design surface want.\n *\n * @param key The query-string parameter, or undefined to keep the state local.\n * @param fallback The value used when the address has none.\n * @returns The value and its setter.\n */\nexport function useUrlState(key: string | undefined, fallback: string | undefined): [string | undefined, (value: string | undefined) => void] {\n const [value, setValue] = useState<string | undefined>(() => (key === undefined ? fallback : readUrlParameter(key) ?? fallback));\n\n useEffect(() => {\n if (key === undefined) return;\n const restore = () => setValue(readUrlParameter(key) ?? fallback);\n window.addEventListener('popstate', restore);\n return () => window.removeEventListener('popstate', restore);\n }, [key, fallback]);\n\n const update = useCallback((next: string | undefined) => {\n setValue(next);\n if (key !== undefined) writeUrlParameter(key, next);\n }, [key]);\n\n return [value, update];\n}\n"],"names":["readUrlParameter","name","window","undefined","value","URLSearchParams","location","search","get","writeUrlParameter","parameters","delete","set","query","toString","history","replaceState","state","pathname","length","hash","useUrlState","key","fallback","setValue","useState","useEffect","restore","addEventListener","removeEventListener","update","useCallback","next"],"mappings":";;AAAA;AACA;AAIA,0IACO,SAASA,gBAAAA,CAAiBC,IAAY,EAAA;IACzC,IAAI,OAAOC,MAAAA,KAAW,WAAA,EAAa,OAAOC,SAAAA;IAC1C,MAAMC,KAAAA,GAAQ,IAAIC,eAAAA,CAAgBH,MAAAA,CAAOI,QAAQ,CAACC,MAAM,CAAA,CAAEC,GAAG,CAACP,IAAAA,CAAAA;IAC9D,OAAOG,KAAAA,KAAU,OAAOD,SAAAA,GAAYC,KAAAA;AACxC;AAEA,gIACO,SAASK,iBAAAA,CAAkBR,IAAY,EAAEG,KAAyB,EAAA;IACrE,IAAI,OAAOF,WAAW,WAAA,EAAa;AACnC,IAAA,MAAMQ,aAAa,IAAIL,eAAAA,CAAgBH,MAAAA,CAAOI,QAAQ,CAACC,MAAM,CAAA;AAC7D,IAAA,IAAIH,UAAUD,SAAAA,IAAaC,KAAAA,KAAU,EAAA,EAAIM,UAAAA,CAAWC,MAAM,CAACV,IAAAA,CAAAA;SACtDS,UAAAA,CAAWE,GAAG,CAACX,IAAAA,EAAMG,KAAAA,CAAAA;IAE1B,MAAMS,KAAAA,GAAQH,WAAWI,QAAQ,EAAA;AACjCZ,IAAAA,MAAAA,CAAOa,OAAO,CAACC,YAAY,CAACd,MAAAA,CAAOa,OAAO,CAACE,KAAK,EAAE,EAAA,EAAI,CAAA,EAAGf,MAAAA,CAAOI,QAAQ,CAACY,QAAQ,CAAA,EAAGL,KAAAA,CAAMM,MAAM,GAAG,CAAA,GAAI,CAAC,CAAC,EAAEN,KAAAA,CAAAA,CAAO,GAAG,EAAA,CAAA,EAAKX,MAAAA,CAAOI,QAAQ,CAACc,IAAI,CAAA,CAAE,CAAA;AACpJ;AAEA;;;;;;;;;;;AAWC,IACM,SAASC,WAAAA,CAAYC,GAAuB,EAAEC,QAA4B,EAAA;IAC7E,MAAM,CAACnB,KAAAA,EAAOoB,QAAAA,CAAS,GAAGC,QAAAA,CAA6B,IAAOH,GAAAA,KAAQnB,SAAAA,GAAYoB,QAAAA,GAAWvB,gBAAAA,CAAiBsB,GAAAA,CAAAA,IAAQC,QAAAA,CAAAA;IAEtHG,SAAAA,CAAU,IAAA;AACN,QAAA,IAAIJ,QAAQnB,SAAAA,EAAW;AACvB,QAAA,MAAMwB,OAAAA,GAAU,IAAMH,QAAAA,CAASxB,gBAAAA,CAAiBsB,GAAAA,CAAAA,IAAQC,QAAAA,CAAAA;QACxDrB,MAAAA,CAAO0B,gBAAgB,CAAC,UAAA,EAAYD,OAAAA,CAAAA;AACpC,QAAA,OAAO,IAAMzB,MAAAA,CAAO2B,mBAAmB,CAAC,UAAA,EAAYF,OAAAA,CAAAA;IACxD,CAAA,EAAG;AAACL,QAAAA,GAAAA;AAAKC,QAAAA;AAAS,KAAA,CAAA;IAElB,MAAMO,MAAAA,GAASC,YAAY,CAACC,IAAAA,GAAAA;QACxBR,QAAAA,CAASQ,IAAAA,CAAAA;QACT,IAAIV,GAAAA,KAAQnB,SAAAA,EAAWM,iBAAAA,CAAkBa,GAAAA,EAAKU,IAAAA,CAAAA;IAClD,CAAA,EAAG;AAACV,QAAAA;AAAI,KAAA,CAAA;IAER,OAAO;AAAClB,QAAAA,KAAAA;AAAO0B,QAAAA;AAAO,KAAA;AAC1B;;"}
@@ -0,0 +1,3 @@
1
+ export declare const compactQuery = "(max-width: 40rem)";
2
+ export declare function useCompactWidth(): boolean;
3
+ //# sourceMappingURL=useCompactWidth.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useCompactWidth.d.ts","sourceRoot":"","sources":["../../../foundation/useCompactWidth.ts"],"names":[],"mappings":"AAMA,eAAO,MAAM,YAAY,uBAAuB,CAAC;AAGjD,wBAAgB,eAAe,IAAI,OAAO,CAazC"}
@@ -0,0 +1,20 @@
1
+ import { useState, useEffect } from 'react';
2
+
3
+ // Copyright (c) Cratis. All rights reserved.
4
+ // Licensed under the MIT license. See LICENSE file in the project root for full license information.
5
+ /** The width below which a screen is a phone's: the collapse rules of Part B 1.3 (list to cards, master-detail to stack) apply under it. */ const compactQuery = '(max-width: 40rem)';
6
+ /** Whether the viewport is phone-wide, kept up to date. Without `matchMedia` (a test environment, server rendering) it is false, so the full layout renders. */ function useCompactWidth() {
7
+ const [compact, setCompact] = useState(()=>typeof window !== 'undefined' && typeof window.matchMedia === 'function' && window.matchMedia(compactQuery).matches);
8
+ useEffect(()=>{
9
+ if (typeof window === 'undefined' || typeof window.matchMedia !== 'function') return;
10
+ const query = window.matchMedia(compactQuery);
11
+ const update = ()=>setCompact(query.matches);
12
+ update();
13
+ query.addEventListener('change', update);
14
+ return ()=>query.removeEventListener('change', update);
15
+ }, []);
16
+ return compact;
17
+ }
18
+
19
+ export { compactQuery, useCompactWidth };
20
+ //# sourceMappingURL=useCompactWidth.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useCompactWidth.js","sources":["../../../foundation/useCompactWidth.ts"],"sourcesContent":["// Copyright (c) Cratis. All rights reserved.\n// Licensed under the MIT license. See LICENSE file in the project root for full license information.\n\nimport { useEffect, useState } from 'react';\n\n/** The width below which a screen is a phone's: the collapse rules of Part B 1.3 (list to cards, master-detail to stack) apply under it. */\nexport const compactQuery = '(max-width: 40rem)';\n\n/** Whether the viewport is phone-wide, kept up to date. Without `matchMedia` (a test environment, server rendering) it is false, so the full layout renders. */\nexport function useCompactWidth(): boolean {\n const [compact, setCompact] = useState(() => typeof window !== 'undefined' && typeof window.matchMedia === 'function' && window.matchMedia(compactQuery).matches);\n\n useEffect(() => {\n if (typeof window === 'undefined' || typeof window.matchMedia !== 'function') return;\n const query = window.matchMedia(compactQuery);\n const update = () => setCompact(query.matches);\n update();\n query.addEventListener('change', update);\n return () => query.removeEventListener('change', update);\n }, []);\n\n return compact;\n}\n"],"names":["compactQuery","useCompactWidth","compact","setCompact","useState","window","matchMedia","matches","useEffect","query","update","addEventListener","removeEventListener"],"mappings":";;AAAA;AACA;AAIA,6IACO,MAAMA,YAAAA,GAAe;AAE5B,iKACO,SAASC,eAAAA,GAAAA;AACZ,IAAA,MAAM,CAACC,OAAAA,EAASC,UAAAA,CAAW,GAAGC,QAAAA,CAAS,IAAM,OAAOC,MAAAA,KAAW,WAAA,IAAe,OAAOA,MAAAA,CAAOC,UAAU,KAAK,UAAA,IAAcD,OAAOC,UAAU,CAACN,cAAcO,OAAO,CAAA;IAEhKC,SAAAA,CAAU,IAAA;AACN,QAAA,IAAI,OAAOH,MAAAA,KAAW,WAAA,IAAe,OAAOA,MAAAA,CAAOC,UAAU,KAAK,UAAA,EAAY;QAC9E,MAAMG,KAAAA,GAAQJ,MAAAA,CAAOC,UAAU,CAACN,YAAAA,CAAAA;AAChC,QAAA,MAAMU,MAAAA,GAAS,IAAMP,UAAAA,CAAWM,KAAAA,CAAMF,OAAO,CAAA;AAC7CG,QAAAA,MAAAA,EAAAA;QACAD,KAAAA,CAAME,gBAAgB,CAAC,QAAA,EAAUD,MAAAA,CAAAA;AACjC,QAAA,OAAO,IAAMD,KAAAA,CAAMG,mBAAmB,CAAC,QAAA,EAAUF,MAAAA,CAAAA;AACrD,IAAA,CAAA,EAAG,EAAE,CAAA;IAEL,OAAOR,OAAAA;AACX;;"}
@@ -0,0 +1,16 @@
1
+ export interface WizardStep {
2
+ id: string;
3
+ label: string;
4
+ valid: boolean;
5
+ }
6
+ export declare enum StepStatus {
7
+ Completed = "completed",
8
+ Current = "current",
9
+ Upcoming = "upcoming"
10
+ }
11
+ export declare function readWizardSteps(value: unknown): WizardStep[];
12
+ export declare function reachableStep(steps: WizardStep[], completed: ReadonlySet<string>, requested: string | undefined): string | undefined;
13
+ export declare function statusOf(completed: ReadonlySet<string>, current: string | undefined, id: string): StepStatus;
14
+ export declare function canOpen(steps: WizardStep[], completed: ReadonlySet<string>, id: string): boolean;
15
+ export declare const neighbour: (steps: WizardStep[], id: string | undefined, direction: -1 | 1) => WizardStep | undefined;
16
+ //# sourceMappingURL=wizardModel.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"wizardModel.d.ts","sourceRoot":"","sources":["../../../foundation/wizardModel.ts"],"names":[],"mappings":"AAIA,MAAM,WAAW,UAAU;IACvB,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IAEd,KAAK,EAAE,OAAO,CAAC;CAClB;AAGD,oBAAY,UAAU;IAClB,SAAS,cAAc;IACvB,OAAO,YAAY;IACnB,QAAQ,aAAa;CACxB;AAGD,wBAAgB,eAAe,CAAC,KAAK,EAAE,OAAO,GAAG,UAAU,EAAE,CAW5D;AAGD,wBAAgB,aAAa,CAAC,KAAK,EAAE,UAAU,EAAE,EAAE,SAAS,EAAE,WAAW,CAAC,MAAM,CAAC,EAAE,SAAS,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,GAAG,SAAS,CAKpI;AAGD,wBAAgB,QAAQ,CAAC,SAAS,EAAE,WAAW,CAAC,MAAM,CAAC,EAAE,OAAO,EAAE,MAAM,GAAG,SAAS,EAAE,EAAE,EAAE,MAAM,GAAG,UAAU,CAG5G;AAGD,wBAAgB,OAAO,CAAC,KAAK,EAAE,UAAU,EAAE,EAAE,SAAS,EAAE,WAAW,CAAC,MAAM,CAAC,EAAE,EAAE,EAAE,MAAM,GAAG,OAAO,CAGhG;AAGD,eAAO,MAAM,SAAS,UAAW,UAAU,EAAE,MAAM,MAAM,GAAG,SAAS,aAAa,CAAC,CAAC,GAAG,CAAC,KAAG,UAAU,GAAG,SAAuE,CAAC"}
@@ -0,0 +1,43 @@
1
+ // Copyright (c) Cratis. All rights reserved.
2
+ // Licensed under the MIT license. See LICENSE file in the project root for full license information.
3
+ /** One step of a wizard. */ /** Where a step stands for the person going through the wizard. */ var StepStatus = /*#__PURE__*/ function(StepStatus) {
4
+ StepStatus["Completed"] = "completed";
5
+ StepStatus["Current"] = "current";
6
+ StepStatus["Upcoming"] = "upcoming";
7
+ return StepStatus;
8
+ }({});
9
+ /** Reads steps, dropping those without an id and repeated ids. */ function readWizardSteps(value) {
10
+ if (!Array.isArray(value)) return [];
11
+ const seen = new Set();
12
+ return value.flatMap((entry)=>{
13
+ if (typeof entry !== 'object' || entry === null || Array.isArray(entry)) return [];
14
+ const { id, label, valid } = entry;
15
+ if (typeof id !== 'string' || id.length === 0 || seen.has(id)) return [];
16
+ seen.add(id);
17
+ return [
18
+ {
19
+ id,
20
+ label: typeof label === 'string' && label.length > 0 ? label : id,
21
+ valid: valid !== false
22
+ }
23
+ ];
24
+ });
25
+ }
26
+ /** The step to show: the requested one when it exists and every step before it is completed, else the furthest step that is reachable. A refresh can never land past a step that was not done. */ function reachableStep(steps, completed, requested) {
27
+ const wanted = steps.findIndex((step)=>step.id === requested);
28
+ let furthest = 0;
29
+ while(furthest < steps.length - 1 && completed.has(steps[furthest].id))furthest++;
30
+ return steps[wanted >= 0 && wanted <= furthest ? wanted : furthest]?.id;
31
+ }
32
+ /** The status of a step: the current one, a completed one (revisitable), or upcoming. */ function statusOf(completed, current, id) {
33
+ if (id === current) return "current";
34
+ return completed.has(id) ? "completed" : "upcoming";
35
+ }
36
+ /** Whether a step may be opened from the progress indicator: a completed one, the current one, or the next one when the current is done. */ function canOpen(steps, completed, id) {
37
+ const index = steps.findIndex((step)=>step.id === id);
38
+ return index === 0 || steps.slice(0, index).every((step)=>completed.has(step.id));
39
+ }
40
+ /** The step before or after another, or undefined at either end. */ const neighbour = (steps, id, direction)=>steps[steps.findIndex((step)=>step.id === id) + direction];
41
+
42
+ export { StepStatus, canOpen, neighbour, reachableStep, readWizardSteps, statusOf };
43
+ //# sourceMappingURL=wizardModel.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"wizardModel.js","sources":["../../../foundation/wizardModel.ts"],"sourcesContent":["// Copyright (c) Cratis. All rights reserved.\n// Licensed under the MIT license. See LICENSE file in the project root for full license information.\n\n/** One step of a wizard. */\nexport interface WizardStep {\n id: string;\n label: string;\n /** Whether the step's own fields are valid; a step that is not valid blocks Next. Defaults to true. */\n valid: boolean;\n}\n\n/** Where a step stands for the person going through the wizard. */\nexport enum StepStatus {\n Completed = 'completed',\n Current = 'current',\n Upcoming = 'upcoming',\n}\n\n/** Reads steps, dropping those without an id and repeated ids. */\nexport function readWizardSteps(value: unknown): WizardStep[] {\n if (!Array.isArray(value)) return [];\n\n const seen = new Set<string>();\n return value.flatMap((entry): WizardStep[] => {\n if (typeof entry !== 'object' || entry === null || Array.isArray(entry)) return [];\n const { id, label, valid } = entry as Record<string, unknown>;\n if (typeof id !== 'string' || id.length === 0 || seen.has(id)) return [];\n seen.add(id);\n return [{ id, label: typeof label === 'string' && label.length > 0 ? label : id, valid: valid !== false }];\n });\n}\n\n/** The step to show: the requested one when it exists and every step before it is completed, else the furthest step that is reachable. A refresh can never land past a step that was not done. */\nexport function reachableStep(steps: WizardStep[], completed: ReadonlySet<string>, requested: string | undefined): string | undefined {\n const wanted = steps.findIndex(step => step.id === requested);\n let furthest = 0;\n while (furthest < steps.length - 1 && completed.has(steps[furthest].id)) furthest++;\n return steps[wanted >= 0 && wanted <= furthest ? wanted : furthest]?.id;\n}\n\n/** The status of a step: the current one, a completed one (revisitable), or upcoming. */\nexport function statusOf(completed: ReadonlySet<string>, current: string | undefined, id: string): StepStatus {\n if (id === current) return StepStatus.Current;\n return completed.has(id) ? StepStatus.Completed : StepStatus.Upcoming;\n}\n\n/** Whether a step may be opened from the progress indicator: a completed one, the current one, or the next one when the current is done. */\nexport function canOpen(steps: WizardStep[], completed: ReadonlySet<string>, id: string): boolean {\n const index = steps.findIndex(step => step.id === id);\n return index === 0 || steps.slice(0, index).every(step => completed.has(step.id));\n}\n\n/** The step before or after another, or undefined at either end. */\nexport const neighbour = (steps: WizardStep[], id: string | undefined, direction: -1 | 1): WizardStep | undefined => steps[steps.findIndex(step => step.id === id) + direction];\n"],"names":["StepStatus","readWizardSteps","value","Array","isArray","seen","Set","flatMap","entry","id","label","valid","length","has","add","reachableStep","steps","completed","requested","wanted","findIndex","step","furthest","statusOf","current","canOpen","index","slice","every","neighbour","direction"],"mappings":"AAAA;AACA;AAEA,iGASO,IAAA,UAAKA,iBAAAA,SAAAA,UAAAA,EAAAA;;;;AAAAA,IAAAA,OAAAA,UAAAA;AAIX,CAAA,CAAA,EAAA;AAED,mEACO,SAASC,eAAAA,CAAgBC,KAAc,EAAA;AAC1C,IAAA,IAAI,CAACC,KAAAA,CAAMC,OAAO,CAACF,KAAAA,CAAAA,EAAQ,OAAO,EAAE;AAEpC,IAAA,MAAMG,OAAO,IAAIC,GAAAA,EAAAA;IACjB,OAAOJ,KAAAA,CAAMK,OAAO,CAAC,CAACC,KAAAA,GAAAA;QAClB,IAAI,OAAOA,KAAAA,KAAU,QAAA,IAAYA,KAAAA,KAAU,IAAA,IAAQL,MAAMC,OAAO,CAACI,KAAAA,CAAAA,EAAQ,OAAO,EAAE;AAClF,QAAA,MAAM,EAAEC,EAAE,EAAEC,KAAK,EAAEC,KAAK,EAAE,GAAGH,KAAAA;AAC7B,QAAA,IAAI,OAAOC,EAAAA,KAAO,QAAA,IAAYA,EAAAA,CAAGG,MAAM,KAAK,CAAA,IAAKP,IAAAA,CAAKQ,GAAG,CAACJ,EAAAA,CAAAA,EAAK,OAAO,EAAE;AACxEJ,QAAAA,IAAAA,CAAKS,GAAG,CAACL,EAAAA,CAAAA;QACT,OAAO;AAAC,YAAA;AAAEA,gBAAAA,EAAAA;AAAIC,gBAAAA,KAAAA,EAAO,OAAOA,KAAAA,KAAU,QAAA,IAAYA,MAAME,MAAM,GAAG,IAAIF,KAAAA,GAAQD,EAAAA;AAAIE,gBAAAA,KAAAA,EAAOA,KAAAA,KAAU;AAAM;AAAE,SAAA;AAC9G,IAAA,CAAA,CAAA;AACJ;AAEA,mMACO,SAASI,aAAAA,CAAcC,KAAmB,EAAEC,SAA8B,EAAEC,SAA6B,EAAA;IAC5G,MAAMC,MAAAA,GAASH,MAAMI,SAAS,CAACC,CAAAA,IAAAA,GAAQA,IAAAA,CAAKZ,EAAE,KAAKS,SAAAA,CAAAA;AACnD,IAAA,IAAII,QAAAA,GAAW,CAAA;AACf,IAAA,MAAOA,QAAAA,GAAWN,KAAAA,CAAMJ,MAAM,GAAG,CAAA,IAAKK,SAAAA,CAAUJ,GAAG,CAACG,KAAK,CAACM,QAAAA,CAAS,CAACb,EAAE,CAAA,CAAGa,QAAAA,EAAAA;IACzE,OAAON,KAAK,CAACG,MAAAA,IAAU,CAAA,IAAKA,UAAUG,QAAAA,GAAWH,MAAAA,GAASG,SAAS,EAAEb,EAAAA;AACzE;AAEA,0FACO,SAASc,QAAAA,CAASN,SAA8B,EAAEO,OAA2B,EAAEf,EAAU,EAAA;AAC5F,IAAA,IAAIA,OAAOe,OAAAA,EAAS,OAAA,SAAA;IACpB,OAAOP,SAAAA,CAAUJ,GAAG,CAACJ,EAAAA,CAAAA,GAAAA,WAAAA,GAAAA,UAAAA;AACzB;AAEA,6IACO,SAASgB,OAAAA,CAAQT,KAAmB,EAAEC,SAA8B,EAAER,EAAU,EAAA;IACnF,MAAMiB,KAAAA,GAAQV,MAAMI,SAAS,CAACC,CAAAA,IAAAA,GAAQA,IAAAA,CAAKZ,EAAE,KAAKA,EAAAA,CAAAA;AAClD,IAAA,OAAOiB,KAAAA,KAAU,CAAA,IAAKV,KAAAA,CAAMW,KAAK,CAAC,CAAA,EAAGD,KAAAA,CAAAA,CAAOE,KAAK,CAACP,CAAAA,IAAAA,GAAQJ,SAAAA,CAAUJ,GAAG,CAACQ,KAAKZ,EAAE,CAAA,CAAA;AACnF;AAEA,qEACO,MAAMoB,SAAAA,GAAY,CAACb,KAAAA,EAAqBP,EAAAA,EAAwBqB,YAA8Cd,KAAK,CAACA,MAAMI,SAAS,CAACC,CAAAA,IAAAA,GAAQA,IAAAA,CAAKZ,EAAE,KAAKA,EAAAA,CAAAA,GAAMqB;;"}
package/dist/esm/index.js CHANGED
@@ -87,6 +87,22 @@ export { buildOrgTree, descendantCount, expandedToDepth, placements, readOrgNode
87
87
  export { OrgTree } from './foundation/OrgTree.js';
88
88
  export { isGranted, readGrants, readPermissions, readRoles, sectionsOf, setGrant } from './foundation/permissionModel.js';
89
89
  export { PermissionMatrix } from './foundation/PermissionMatrix.js';
90
+ export { readUrlParameter, useUrlState, writeUrlParameter } from './foundation/urlState.js';
91
+ export { compactQuery, useCompactWidth } from './foundation/useCompactWidth.js';
92
+ export { CellKind, ListVariant, filterRows, formatSort, groupRows, nextSort, paginate, parseSelection, parseSort, readListColumns, readListVariant, rowId, searchRows, sortRows, textOf, toggleSelection } from './foundation/listModel.js';
93
+ export { DataList } from './foundation/DataList.js';
94
+ export { activeTab, readTabs, stepTab } from './foundation/tabModel.js';
95
+ export { TabSet } from './foundation/TabSet.js';
96
+ export { SubNavigation } from './foundation/SubNavigation.js';
97
+ export { BulkBar, selectionCount } from './foundation/BulkBar.js';
98
+ export { GlobalSearch } from './foundation/GlobalSearch.js';
99
+ export { NotificationsButton } from './foundation/NotificationsButton.js';
100
+ export { cardsOf, moveCard, overLimit, readCards, readColumnsOfBoard } from './foundation/boardModel.js';
101
+ export { KanbanBoard } from './foundation/KanbanBoard.js';
102
+ export { StepStatus, canOpen, neighbour, reachableStep, readWizardSteps, statusOf } from './foundation/wizardModel.js';
103
+ export { WizardProgress } from './foundation/WizardProgress.js';
104
+ export { FormFieldType, FormMode, addressLines, isFilled, mapServerErrors, problemOf, readFormMode, readFormSections, sameValues, validate } from './foundation/formModel.js';
105
+ export { FormScreenBody } from './foundation/FormScreenBody.js';
90
106
  export { bindable, foundationDescriptors, list, standardStateProperties } from './foundation/foundationDescriptors.js';
91
107
  export { foundationComponentMetadata } from './foundation/foundationComponentMetadata.js';
92
108
  export { foundationElement, foundationSamples } from './foundation/foundationSamples.js';
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
1
+ {"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}