@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
package/dist/utils.js DELETED
@@ -1,557 +0,0 @@
1
- import isNumber from 'lodash-es/isNumber';
2
- import isObject from 'lodash-es/isObject';
3
- import { useEffect, isValidElement } from 'react';
4
- import { isValidElementType } from 'react-is';
5
- import isString from 'lodash-es/isString';
6
- import { pascalCase, kebabCase, snakeCase } from 'change-case';
7
- export { kebabCase as dashCase, pascalCase, snakeCase } from 'change-case';
8
- import isFunction from 'lodash-es/isFunction';
9
- import isArray from 'lodash-es/isArray';
10
- import get from 'lodash-es/get';
11
- import capitalize from 'lodash-es/capitalize';
12
- import EventEmitter from 'wolfy87-eventemitter';
13
- import slugify from 'slugify';
14
-
15
- function checkClickable(el, options = {}, parentDistance = 1) {
16
- const {
17
- maxParentDistance = 5,
18
- tags = ['BUTTON', 'A', 'INPUT', 'I', 'TEXTAREA']
19
- } = options || {};
20
- const {
21
- tagName = null,
22
- parentNode = null
23
- } = el || {};
24
- if (tagName === 'BODY') {
25
- return false;
26
- }
27
-
28
- // Check if video is suspended
29
- // if (
30
- // tagName === 'VIDEO' &&
31
- // typeof dataset.isSuspended !== 'undefined' &&
32
- // (dataset.isSuspended === 'true' || dataset.isSuspended === true)
33
- // ) {
34
- // return true;
35
- // }
36
-
37
- if (tagName && tags.map(it => it.toLowerCase()).indexOf(tagName.toLowerCase()) !== -1) {
38
- return true;
39
- }
40
- if (parentDistance < maxParentDistance) {
41
- return checkClickable(parentNode, options, parentDistance + 1);
42
- }
43
- return false;
44
- }
45
-
46
- // Convert a camelCase style property (ex: backgroundColor) to its CSS kebab-case
47
- // name (ex: background-color). Custom properties (--foo) are kept as is.
48
- function toCssProperty(key) {
49
- if (key.startsWith('--')) {
50
- return key;
51
- }
52
- const property = key.replace(/[A-Z]/g, match => `-${match.toLowerCase()}`);
53
- return property.startsWith('ms-') ? `-${property}` : property;
54
- }
55
- function convertStyleToString(style) {
56
- return style !== null ? Object.keys(style).map(key => `${toCssProperty(key)}:${isNumber(style[key]) ? `${style[key]}px` : style[key]};`).join('\n') : '';
57
- }
58
-
59
- function createNullableOnChange(onChange = null) {
60
- return function nullableOnChange(newValue) {
61
- let nullableValue = newValue;
62
- if (isObject(newValue)) {
63
- const allNull = Object.keys(newValue).reduce((acc, key) => acc && newValue[key] === null, true);
64
- if (allNull) {
65
- nullableValue = null;
66
- }
67
- }
68
- if (onChange !== null) {
69
- onChange(nullableValue);
70
- }
71
- };
72
- }
73
-
74
- function createUseEvent(eventsManager) {
75
- return function useEvent(event, callback, enabled = true) {
76
- useEffect(() => {
77
- if (enabled && eventsManager !== null) {
78
- eventsManager.subscribe(event, callback);
79
- }
80
- return () => {
81
- if (enabled && eventsManager !== null) {
82
- eventsManager.unsubscribe(event, callback);
83
- }
84
- };
85
- }, [eventsManager, event, callback, enabled]);
86
- };
87
- }
88
-
89
- /*! clipboard-copy. MIT License. Feross Aboukhadijeh <https://feross.org/opensource> */
90
-
91
- // @note vendoring this: https://github.com/feross/clipboard-copy/blob/master/index.js
92
- // we might want to add that to the npm deps and just use it
93
-
94
- function makeError() {
95
- return new DOMException('The request is not allowed', 'NotAllowedError');
96
- }
97
- async function copyClipboardApi(text) {
98
- // Use the Async Clipboard API when available. Requires a secure browsing
99
- // context (i.e. HTTPS)
100
- if (!navigator.clipboard) {
101
- throw makeError();
102
- }
103
- return navigator.clipboard.writeText(text);
104
- }
105
- async function copyExecCommand(text) {
106
- // Put the text to copy into a <span>
107
- const span = document.createElement('span');
108
- span.textContent = text;
109
-
110
- // Preserve consecutive spaces and newlines
111
- span.style.whiteSpace = 'pre';
112
- span.style.webkitUserSelect = 'auto';
113
- span.style.userSelect = 'all';
114
-
115
- // Add the <span> to the page
116
- document.body.appendChild(span);
117
-
118
- // Make a selection object representing the range of text selected by the user
119
- const selection = window.getSelection();
120
- const range = window.document.createRange();
121
- selection?.removeAllRanges();
122
- range.selectNode(span);
123
- selection?.addRange(range);
124
-
125
- // Copy text to the clipboard
126
- let success = false;
127
- try {
128
- success = window.document.execCommand('copy');
129
- } finally {
130
- // Cleanup
131
- selection?.removeAllRanges();
132
- window.document.body.removeChild(span);
133
- }
134
- if (!success) throw makeError();
135
- }
136
- async function copyToClipboard(text) {
137
- try {
138
- await copyClipboardApi(text);
139
- } catch (err) {
140
- // ...Otherwise, use document.execCommand() fallback
141
- try {
142
- await copyExecCommand(text);
143
- } catch (err2) {
144
- throw err2 || err || makeError();
145
- }
146
- }
147
- }
148
-
149
- function flattenComponents(components, prefix = null) {
150
- if (components === null) {
151
- return null;
152
- }
153
- return Object.keys(components).reduce((newMap, key) => {
154
- const path = prefix !== null ? `${prefix}.${key}` : key;
155
- return isValidElementType(components[key]) ? {
156
- ...newMap,
157
- [path]: components[key]
158
- } : {
159
- ...newMap,
160
- ...flattenComponents(components[key], path)
161
- };
162
- }, {});
163
- }
164
-
165
- function formatDuration(timeInSeconds) {
166
- const numberOfSeconds = parseInt(String(timeInSeconds), 10); // don't forget the second param
167
- let hours = Math.floor(numberOfSeconds / 3600);
168
- let minutes = Math.floor((numberOfSeconds - hours * 3600) / 60);
169
- let seconds = numberOfSeconds - hours * 3600 - minutes * 60;
170
- if (hours < 10) {
171
- hours = `0${hours}`;
172
- }
173
- if (minutes < 10) {
174
- minutes = `0${minutes}`;
175
- }
176
- if (seconds < 10) {
177
- seconds = `0${seconds}`;
178
- }
179
- return `${hours}:${minutes}:${seconds}`;
180
- }
181
-
182
- function getComponent(component) {
183
- const {
184
- component: name = null,
185
- ...props
186
- } = isString(component) ? {
187
- component
188
- } : component || {};
189
- return {
190
- name,
191
- props: Object.keys(props).length > 0 ? props : null
192
- };
193
- }
194
-
195
- function getColumnFromField(field) {
196
- if (field === null) {
197
- return null;
198
- }
199
- const {
200
- name,
201
- components: {
202
- display = null
203
- } = {},
204
- label
205
- } = field;
206
- const {
207
- name: componentName = null,
208
- props: componentProps = null
209
- } = getComponent(display);
210
- return {
211
- ...componentProps,
212
- id: name,
213
- label,
214
- valueKey: name,
215
- component: componentName,
216
- field
217
- };
218
- }
219
- function getColumnsWithFields(resource, columns) {
220
- const {
221
- fields = []
222
- } = resource;
223
- const newColumns = columns !== null ? columns.map(column => {
224
- const {
225
- id: colId = null,
226
- field: colField = null,
227
- ...otherProps
228
- } = isString(column) ? {
229
- field: column
230
- } : column;
231
- const fieldName = colField || colId;
232
- return {
233
- ...(fieldName !== null ? getColumnFromField(fields.find(it => it.name === fieldName) || null) : null),
234
- ...otherProps,
235
- id: colId || (isString(column) ? column : null) || null
236
- };
237
- }).filter(it => it !== null) : [];
238
- return newColumns;
239
- }
240
-
241
- function getComponentFromName(name = null, components = {}, defaultComponent = null) {
242
- if (components === null || name === null) {
243
- return !isString(defaultComponent) ? defaultComponent : null;
244
- }
245
- if (isObject(name) || isFunction(name)) {
246
- return name;
247
- }
248
- const pascalName = pascalCase(name);
249
- const pascalDefaultName = isString(defaultComponent) ? pascalCase(defaultComponent) : null;
250
- const component = components[pascalName] || components[name] || (isString(pascalDefaultName) ? components[pascalDefaultName] : null) || (isString(defaultComponent) ? components[defaultComponent] : null) || defaultComponent;
251
- if (!component) {
252
- console.warn('Could not find component from name', name, pascalName, components);
253
- }
254
- return component;
255
- }
256
-
257
- function getDefinitionFromId(id = null, items = [], defaultObject = null) {
258
- if (items === null || id === null || isArray(items) && items.length === 0) {
259
- return defaultObject;
260
- }
261
- const pascalId = pascalCase(id);
262
- return items.find(i => i.id === pascalId) || items.find(i => i.id === id) || defaultObject;
263
- }
264
-
265
- /**
266
- * Returns a message for each key of the definition that is not supported anymore,
267
- * with the key to use instead. Without it, these keys would be silently ignored.
268
- */
269
- function getDefinitionWarnings(definition) {
270
- if (!isObject(definition)) {
271
- return [];
272
- }
273
- const {
274
- components = null,
275
- intl = null,
276
- theme = null,
277
- resources = []
278
- } = definition;
279
- const {
280
- values = null
281
- } = intl || {};
282
- return [...(components !== null ? ['`components` is not supported: give the custom components to the `components` prop of the container.'] : []), ...(values !== null ? ['`intl.values` is not supported: set the values of each resource in `resources[].intl.values`.'] : []), ...Object.keys(theme || {}).filter(key => key !== 'colorScheme').map(key => `\`theme.${key}\` is not supported: only \`theme.colorScheme\` is.`), ...(resources || []).reduce((warnings, resource) => {
283
- const {
284
- id,
285
- components: resourceComponents = null,
286
- columns = null
287
- } = resource;
288
- return [...warnings, ...(resourceComponents !== null ? [`\`resources[${id}].components\` is not supported: use \`index.component\`, \`forms.default.component\` and \`pages\`.`] : []), ...(columns !== null ? [`\`resources[${id}].columns\` is not supported: use \`index.columns\`.`] : [])];
289
- }, [])];
290
- }
291
-
292
- function getDisplayName({
293
- displayName = null,
294
- name = null
295
- }) {
296
- return displayName || name || 'Component';
297
- }
298
-
299
- function getFieldByName(fields, name) {
300
- return fields.reduce((foundField, it) => {
301
- if (foundField !== null) {
302
- return foundField;
303
- }
304
- const {
305
- name: fieldName = null,
306
- fields: subFields = []
307
- } = it;
308
- if (name !== null && fieldName === name) {
309
- return it;
310
- }
311
- return getFieldByName(subFields, name);
312
- }, null);
313
- }
314
-
315
- function getFieldFromPath(path, fields, fieldManager) {
316
- const initialField = {
317
- fields
318
- };
319
- return (isArray(path) ? path : [path]).reduce((foundField, key) => {
320
- if (foundField === null) {
321
- return null;
322
- }
323
- const {
324
- type = null,
325
- fields: fieldFields = null,
326
- field = null
327
- } = foundField;
328
- const finalType = field !== null ? field.type || type : type;
329
- const {
330
- fields: subFields = null,
331
- settings = null,
332
- itemsField = null
333
- } = finalType !== null ? fieldManager.getDefinition(finalType) : foundField;
334
- if (itemsField !== null && key.match(/^[0-9]+$/)) {
335
- return {
336
- ...itemsField,
337
- name: (isArray(path) ? path : [path]).join('/'),
338
- listItems: true
339
- };
340
- }
341
- return getFieldByName([...(fieldFields || []), ...(subFields || []), ...(settings || [])], key);
342
- }, initialField);
343
- }
344
-
345
- function getFileName(url = null) {
346
- if (url === null || typeof url.match === 'undefined') {
347
- return null;
348
- }
349
- const match = url.match(/([^/]+)(\?.*)?$/);
350
- return match ? match[1] : url;
351
- }
352
-
353
- function getPathValue(it, path) {
354
- const value = get(it, path, null);
355
- return value !== null && isObject(value) ? Object.values(value).reduce((foundValue, val) => foundValue !== null ? foundValue : val, null) : value;
356
- }
357
-
358
- function getSelectOptions(options) {
359
- return options.map(it => {
360
- if (isObject(it)) {
361
- const {
362
- label = null,
363
- value
364
- } = it;
365
- if (label === null) {
366
- return {
367
- value,
368
- label: capitalize(`${value}`)
369
- };
370
- }
371
- return {
372
- value,
373
- label: /*#__PURE__*/isValidElement(label) ? label : `${label}`
374
- };
375
- }
376
- return {
377
- value: it,
378
- label: capitalize(`${it}`)
379
- };
380
- });
381
- }
382
-
383
- function isMessage(message) {
384
- return isObject(message) && (typeof message.defaultMessage !== 'undefined' || typeof message.id !== 'undefined');
385
- }
386
-
387
- /**
388
- * Locale loader
389
- */
390
- const loaders = {};
391
- const events = new EventEmitter();
392
- function loadPackage(name, loader) {
393
- if (typeof loaders[name] === 'undefined') {
394
- loaders[name] = {
395
- loader: loader().then(response => {
396
- loaders[name].loader = null;
397
- loaders[name].loaded = true;
398
- loaders[name].resource = response;
399
- events.emit(`loaded:${name}`, response);
400
- return response;
401
- }, error => {
402
- // Remove the failed loader so a later call can retry
403
- delete loaders[name];
404
- throw error;
405
- }),
406
- loaded: false,
407
- resource: null
408
- };
409
- }
410
- const {
411
- loaded = false,
412
- resource = null,
413
- loader: currentLoader = null
414
- } = loaders[name];
415
- if (loaded === true) {
416
- return Promise.resolve(resource);
417
- }
418
- return currentLoader;
419
- }
420
-
421
- function mergeRefs(...refs) {
422
- return value => {
423
- refs.forEach(ref => {
424
- if (typeof ref === 'function') {
425
- ref(value);
426
- } else if (ref !== null) {
427
- ref.current = value;
428
- }
429
- });
430
- };
431
- }
432
-
433
- // A date without time (2026-10-01), or a date at midnight with or without a timezone
434
- // (2026-10-01T00:00:00-04:00, 2026-10-01T00:00:00.000000Z, 2026-10-01 00:00:00)
435
- const DATE_ONLY_PATTERN = /^(\d{4})-(\d{2})-(\d{2})(?:[T ]00:00(?::00(?:\.0+)?)?(?:Z|[+-]\d{2}:?\d{2})?)?$/;
436
-
437
- /**
438
- * Parse a date-only value as a local date, ignoring its timezone.
439
- *
440
- * A date stored at midnight represents a calendar day, not an instant: converting it to the
441
- * browser timezone would show the previous day west of the server timezone (ex: a date saved as
442
- * 2026-10-01 00:00 UTC is 2026-09-30 20:00 in Montreal). Returns null when the value has a time,
443
- * so the caller can parse it as an instant.
444
- */
445
- function parseDateOnly(value) {
446
- if (typeof value !== 'string') {
447
- return null;
448
- }
449
- const match = DATE_ONLY_PATTERN.exec(value.trim());
450
- if (match === null) {
451
- return null;
452
- }
453
- const [, year, month, day] = match;
454
- return new Date(parseInt(year, 10), parseInt(month, 10) - 1, parseInt(day, 10));
455
- }
456
-
457
- function toggleSelectedItem(items, item, {
458
- multiple: multipleSelection = false
459
- } = {}) {
460
- const {
461
- id: itemId = null
462
- } = item || {};
463
- const currentItem = (items || []).find(({
464
- id
465
- }) => id === itemId) || null;
466
- if (currentItem !== null) {
467
- return items.filter(({
468
- id
469
- }) => id !== itemId);
470
- }
471
- return multipleSelection ? [...(items || []), item] : [item];
472
- }
473
- function selectItems(items, itemsToSelect) {
474
- const newItems = itemsToSelect.filter(item => {
475
- const {
476
- id: itemId = null
477
- } = item || {};
478
- const currentItem = (items || []).find(({
479
- id
480
- }) => id === itemId) || null;
481
- return currentItem === null;
482
- });
483
- return newItems.length > 0 ? [...(items || []), ...newItems] : items;
484
- }
485
- function unselectItems(items, itemsToUnselect) {
486
- const newItems = (items || []).filter(item => {
487
- const {
488
- id: itemId = null
489
- } = item || {};
490
- const currentItem = itemsToUnselect.find(({
491
- id
492
- }) => id === itemId) || null;
493
- return currentItem === null;
494
- });
495
- return items !== null && newItems.length !== items.length ? newItems : items;
496
- }
497
-
498
- function setValue(value, keyParts, fieldValue) {
499
- const key = keyParts.shift();
500
- if (!key) {
501
- return value;
502
- }
503
- const isArray = key.match(/^[0-9]+$/) !== null;
504
- if (value !== null || fieldValue !== null) {
505
- if (isArray) {
506
- const index = parseInt(key, 10);
507
- const arrayValue = Array.isArray(value) ? value : [];
508
- let newArrayValue;
509
- if (fieldValue !== null) {
510
- newArrayValue = [...arrayValue];
511
- // Pad with null so an out-of-range index lands at the right position
512
- while (newArrayValue.length < index) {
513
- newArrayValue.push(null);
514
- }
515
- newArrayValue[index] = keyParts.length > 0 ? setValue(arrayValue[index] || null, keyParts, fieldValue) : fieldValue;
516
- } else {
517
- newArrayValue = [...arrayValue.slice(0, index), ...arrayValue.slice(index + 1)];
518
- }
519
- return newArrayValue.length > 0 ? newArrayValue : null;
520
- }
521
- const objectValue = value;
522
- return {
523
- ...objectValue,
524
- [key]: keyParts.length > 0 ? setValue(objectValue !== null ? objectValue[key] || null : null, keyParts, fieldValue) : fieldValue
525
- };
526
- }
527
- return null;
528
- }
529
-
530
- function slug(str, separator = null) {
531
- let toSlug;
532
- if (separator === '-') {
533
- toSlug = kebabCase(str);
534
- } else {
535
- toSlug = snakeCase(str);
536
- }
537
- return slugify(toSlug, {
538
- lower: true
539
- });
540
- }
541
-
542
- function validateFields(fields, value) {
543
- return fields.reduce((acc, field) => {
544
- if (acc === true) {
545
- if (field.type === 'fields' && field.fields) {
546
- return validateFields(field.fields, value);
547
- }
548
- const val = value && field.name ? value[field.name] : null;
549
- // Only null, undefined and empty strings are considered missing (0 and false are valid)
550
- const isMissing = val === null || typeof val === 'undefined' || val === '';
551
- return !(field.required && isMissing);
552
- }
553
- return acc;
554
- }, true);
555
- }
556
-
557
- export { checkClickable, convertStyleToString, copyToClipboard, createNullableOnChange, createUseEvent, flattenComponents, formatDuration, getColumnsWithFields, getComponent, getComponentFromName, getDefinitionFromId, getDefinitionWarnings, getDisplayName, getFieldByName, getFieldFromPath, getFileName, getPathValue, getSelectOptions, isMessage, loadPackage, mergeRefs, parseDateOnly, selectItems, setValue as setFieldValue, slug, toggleSelectedItem, unselectItems, validateFields };