@panneau/core 4.0.78 → 4.1.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.
- package/dist/contexts.d.ts +237 -168
- package/dist/contexts.js +20 -23
- package/dist/hooks.d.ts +58 -40
- package/dist/hooks.js +780 -338
- package/dist/index.d.ts +258 -201
- package/dist/index.js +23 -29
- package/dist/utils.d.ts +59 -17
- package/dist/utils.js +79 -31
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -1,13 +1,12 @@
|
|
|
1
|
+
import isObject from 'lodash-es/isObject';
|
|
1
2
|
import { flattenComponents, getComponentFromName } from '@panneau/core/utils';
|
|
2
|
-
import { isObject } from 'lodash';
|
|
3
3
|
import isArray from 'lodash-es/isArray';
|
|
4
4
|
import uniqBy from 'lodash-es/uniqBy';
|
|
5
5
|
import EventEmitter from 'wolfy87-eventemitter';
|
|
6
|
-
import isObject$1 from 'lodash-es/isObject';
|
|
7
|
-
import uniq from 'lodash-es/uniq';
|
|
8
6
|
import { Tracking as Tracking$1 } from '@folklore/tracking';
|
|
9
7
|
|
|
10
8
|
class ComponentsManager {
|
|
9
|
+
// Nested components maps are flattened with their key as namespace
|
|
11
10
|
constructor(components = {}) {
|
|
12
11
|
this.components = flattenComponents(components);
|
|
13
12
|
}
|
|
@@ -175,7 +174,7 @@ class FontsParser {
|
|
|
175
174
|
theme = null,
|
|
176
175
|
components = []
|
|
177
176
|
} = story || {};
|
|
178
|
-
const fonts =
|
|
177
|
+
const fonts = uniqBy(components.reduce((currentFonts, screen) => {
|
|
179
178
|
const {
|
|
180
179
|
type
|
|
181
180
|
} = screen;
|
|
@@ -196,7 +195,7 @@ class FontsParser {
|
|
|
196
195
|
return fonts.length > 0 || themeFonts.length > 0 ? {
|
|
197
196
|
...story,
|
|
198
197
|
theme: newTheme,
|
|
199
|
-
fonts:
|
|
198
|
+
fonts: uniqBy([...themeFonts, ...fonts], 'name')
|
|
200
199
|
} : story;
|
|
201
200
|
}
|
|
202
201
|
return fonts.length > 0 ? {
|
|
@@ -244,9 +243,9 @@ class FontsParser {
|
|
|
244
243
|
const value = data[key];
|
|
245
244
|
let font = null;
|
|
246
245
|
let subFonts = null;
|
|
247
|
-
if (patternMatch && isObject
|
|
246
|
+
if (patternMatch && isObject(value) && FontsParser.valueIsFont(value)) {
|
|
248
247
|
font = value;
|
|
249
|
-
} else if (isObject
|
|
248
|
+
} else if (isObject(value) || isArray(value)) {
|
|
250
249
|
subFonts = FontsParser.extractFontsWithPaths(value, patterns, path);
|
|
251
250
|
}
|
|
252
251
|
return subFonts !== null || font !== null ? [...currentFonts, ...(subFonts || []), ...(font !== null ? [font] : [])] : currentFonts;
|
|
@@ -259,7 +258,7 @@ class FormsManager extends DefinitionsManager {}
|
|
|
259
258
|
class MediasParser {
|
|
260
259
|
constructor({
|
|
261
260
|
fieldsManager,
|
|
262
|
-
screensManager
|
|
261
|
+
screensManager = null
|
|
263
262
|
}) {
|
|
264
263
|
this.fieldsManager = fieldsManager;
|
|
265
264
|
this.screensManager = screensManager;
|
|
@@ -286,7 +285,7 @@ class MediasParser {
|
|
|
286
285
|
} = screen;
|
|
287
286
|
const {
|
|
288
287
|
fields = []
|
|
289
|
-
} = this.screensManager
|
|
288
|
+
} = this.screensManager?.getDefinition(type) || {};
|
|
290
289
|
const fieldsPattern = this.getMediaFieldsPattern(fields);
|
|
291
290
|
const {
|
|
292
291
|
data: newScreen,
|
|
@@ -346,7 +345,7 @@ class MediasParser {
|
|
|
346
345
|
} = screen;
|
|
347
346
|
const {
|
|
348
347
|
fields = []
|
|
349
|
-
} = this.screensManager
|
|
348
|
+
} = this.screensManager?.getDefinition(type) || {};
|
|
350
349
|
const fieldsPattern = this.getMediaFieldsPattern(fields);
|
|
351
350
|
return MediasParser.replacePathsWithMedias(screen, medias, fieldsPattern);
|
|
352
351
|
}) : components;
|
|
@@ -405,9 +404,9 @@ class MediasParser {
|
|
|
405
404
|
const value = data[key];
|
|
406
405
|
let newValue;
|
|
407
406
|
if (patternMatch) {
|
|
408
|
-
newValue = isObject
|
|
407
|
+
newValue = isObject(value) ? value : medias[value] || null;
|
|
409
408
|
} else {
|
|
410
|
-
newValue = isObject
|
|
409
|
+
newValue = isObject(value) || isArray(value) ? MediasParser.replacePathsWithMedias(value, medias, patterns, path) : value;
|
|
411
410
|
}
|
|
412
411
|
return dataIsArray ? [...newData, newValue] : {
|
|
413
412
|
...newData,
|
|
@@ -433,11 +432,11 @@ class MediasParser {
|
|
|
433
432
|
let newValue;
|
|
434
433
|
let media = null;
|
|
435
434
|
let subMedias = null;
|
|
436
|
-
if (patternMatch && isObject
|
|
435
|
+
if (patternMatch && isObject(value)) {
|
|
437
436
|
const mediaPath = MediasParser.getMediaPath(value);
|
|
438
437
|
newValue = mediaPath !== null ? mediaPath : value;
|
|
439
438
|
media = mediaPath !== null ? value : null;
|
|
440
|
-
} else if (isObject
|
|
439
|
+
} else if (isObject(value) || isArray(value)) {
|
|
441
440
|
const subReturn = MediasParser.replaceMediasWithPaths(value, patterns, medias, path);
|
|
442
441
|
newValue = subReturn.data;
|
|
443
442
|
subMedias = subReturn.medias;
|
|
@@ -459,7 +458,7 @@ class MediasParser {
|
|
|
459
458
|
}
|
|
460
459
|
};
|
|
461
460
|
}, {
|
|
462
|
-
data:
|
|
461
|
+
data: dataIsArray ? [] : {},
|
|
463
462
|
medias
|
|
464
463
|
});
|
|
465
464
|
}
|
|
@@ -467,16 +466,6 @@ class MediasParser {
|
|
|
467
466
|
|
|
468
467
|
/* eslint-disable no-console */
|
|
469
468
|
class Tracking extends Tracking$1 {
|
|
470
|
-
constructor(opts = {}) {
|
|
471
|
-
super(opts);
|
|
472
|
-
const {
|
|
473
|
-
variables = null
|
|
474
|
-
} = this.options;
|
|
475
|
-
this.variables = null;
|
|
476
|
-
if (variables !== null) {
|
|
477
|
-
this.setVariables(variables);
|
|
478
|
-
}
|
|
479
|
-
}
|
|
480
469
|
setVariables(variables) {
|
|
481
470
|
this.variables = variables;
|
|
482
471
|
if (variables !== null) {
|
|
@@ -499,10 +488,15 @@ class Tracking extends Tracking$1 {
|
|
|
499
488
|
};
|
|
500
489
|
this.push(data);
|
|
501
490
|
}
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
491
|
+
|
|
492
|
+
// The options can also be a value, to stay compatible with the base trackEvent signature
|
|
493
|
+
trackEvent(category, action, label, options = {}) {
|
|
494
|
+
const {
|
|
495
|
+
value = null,
|
|
496
|
+
...opts
|
|
497
|
+
} = isObject(options) ? options : {
|
|
498
|
+
value: options
|
|
499
|
+
};
|
|
506
500
|
const data = {
|
|
507
501
|
...opts,
|
|
508
502
|
event: 'eventInteraction',
|
package/dist/utils.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export { kebabCase as dashCase, pascalCase, snakeCase } from 'change-case';
|
|
2
|
-
import { ElementType, ReactNode, ReactElement, ForwardedRef } from 'react';
|
|
3
|
-
import { MessageDescriptor } from 'react-intl';
|
|
2
|
+
import { ElementType, ComponentProps, ReactNode, ReactElement, ForwardedRef } from 'react';
|
|
3
|
+
import { FormattedMessage, MessageDescriptor } from 'react-intl';
|
|
4
4
|
|
|
5
5
|
interface CheckClickableOptions {
|
|
6
6
|
maxParentDistance?: number;
|
|
@@ -26,11 +26,6 @@ declare function createUseEvent(eventsManager: EventsManager | null): (event: st
|
|
|
26
26
|
/*! clipboard-copy. MIT License. Feross Aboukhadijeh <https://feross.org/opensource> */
|
|
27
27
|
declare function copyToClipboard(text: string): Promise<void>;
|
|
28
28
|
|
|
29
|
-
type ComponentMap = Record<string, ElementType | Record<string, unknown>>;
|
|
30
|
-
declare function flattenComponents(components: ComponentMap | null, prefix?: string | null): Record<string, ElementType> | null;
|
|
31
|
-
|
|
32
|
-
declare function formatDuration(timeInSeconds: number | string): string;
|
|
33
|
-
|
|
34
29
|
/**
|
|
35
30
|
* Core UI Types
|
|
36
31
|
*/
|
|
@@ -39,10 +34,22 @@ declare function formatDuration(timeInSeconds: number | string): string;
|
|
|
39
34
|
* Internationalization message definition
|
|
40
35
|
*/
|
|
41
36
|
type Message = MessageDescriptor;
|
|
37
|
+
/**
|
|
38
|
+
* Values that can be interpolated in an internationalization message
|
|
39
|
+
* (the `values` prop of FormattedMessage)
|
|
40
|
+
*/
|
|
41
|
+
type MessageValues = NonNullable<ComponentProps<typeof FormattedMessage>['values']>;
|
|
42
|
+
type MessageValue = MessageValues[string];
|
|
42
43
|
/**
|
|
43
44
|
* Label can be either a message object or any React node
|
|
44
45
|
*/
|
|
45
46
|
type Label = Message | ReactNode;
|
|
47
|
+
/**
|
|
48
|
+
* Map of components, can be nested by namespace (ex: { fields: { text: TextField } })
|
|
49
|
+
*/
|
|
50
|
+
interface ComponentsMap {
|
|
51
|
+
[name: string]: ElementType | ComponentsMap;
|
|
52
|
+
}
|
|
46
53
|
/**
|
|
47
54
|
* Form control size
|
|
48
55
|
*/
|
|
@@ -106,8 +113,6 @@ interface Form {
|
|
|
106
113
|
submitButtonLabel?: Label | null;
|
|
107
114
|
[key: string]: unknown;
|
|
108
115
|
}
|
|
109
|
-
interface FormDefinition extends Form, Definition {
|
|
110
|
-
}
|
|
111
116
|
|
|
112
117
|
/**
|
|
113
118
|
* Resource Types
|
|
@@ -120,6 +125,7 @@ interface FormDefinition extends Form, Definition {
|
|
|
120
125
|
type TableColumn = string | Column;
|
|
121
126
|
interface Column extends Record<string, unknown> {
|
|
122
127
|
id?: string | null;
|
|
128
|
+
name?: string | null;
|
|
123
129
|
label?: unknown;
|
|
124
130
|
path?: string;
|
|
125
131
|
valueKey?: string;
|
|
@@ -145,7 +151,7 @@ type ActionDefinition = string | Action;
|
|
|
145
151
|
interface ResourceIntlValues {
|
|
146
152
|
a_singular?: string;
|
|
147
153
|
a_plural?: string;
|
|
148
|
-
[key: string]:
|
|
154
|
+
[key: string]: MessageValue;
|
|
149
155
|
}
|
|
150
156
|
/**
|
|
151
157
|
* Internationalization configuration
|
|
@@ -173,13 +179,32 @@ interface ResourcePages {
|
|
|
173
179
|
duplicate?: PageDefinition;
|
|
174
180
|
[key: string]: PageDefinition | undefined;
|
|
175
181
|
}
|
|
182
|
+
/**
|
|
183
|
+
* Form definition of a resource (the id is optional, the form is identified by its key)
|
|
184
|
+
*/
|
|
185
|
+
interface ResourceForm extends Form {
|
|
186
|
+
id?: string;
|
|
187
|
+
component?: string | ElementType;
|
|
188
|
+
}
|
|
176
189
|
interface ResourceForms {
|
|
177
|
-
default
|
|
178
|
-
create
|
|
179
|
-
edit
|
|
180
|
-
delete
|
|
181
|
-
modal
|
|
182
|
-
[key: string]:
|
|
190
|
+
default?: ResourceForm | null;
|
|
191
|
+
create?: ResourceForm | null;
|
|
192
|
+
edit?: ResourceForm | null;
|
|
193
|
+
delete?: ResourceForm | null;
|
|
194
|
+
modal?: ResourceForm | null;
|
|
195
|
+
[key: string]: ResourceForm | null | undefined;
|
|
196
|
+
}
|
|
197
|
+
/**
|
|
198
|
+
* Resource type (sub-type of a resource, ex: page types)
|
|
199
|
+
*/
|
|
200
|
+
interface ResourceType {
|
|
201
|
+
id?: string;
|
|
202
|
+
name?: string;
|
|
203
|
+
fields?: Field[];
|
|
204
|
+
settings?: {
|
|
205
|
+
canCreate?: boolean;
|
|
206
|
+
[key: string]: unknown;
|
|
207
|
+
};
|
|
183
208
|
}
|
|
184
209
|
interface Resource {
|
|
185
210
|
id: string;
|
|
@@ -191,6 +216,7 @@ interface Resource {
|
|
|
191
216
|
settings?: Record<string, unknown>;
|
|
192
217
|
extraRoutes?: RouteDefinition[];
|
|
193
218
|
pages?: ResourcePages;
|
|
219
|
+
types?: ResourceType[];
|
|
194
220
|
}
|
|
195
221
|
/**
|
|
196
222
|
* Filter definition
|
|
@@ -201,6 +227,10 @@ interface Filter {
|
|
|
201
227
|
[key: string]: unknown;
|
|
202
228
|
}
|
|
203
229
|
|
|
230
|
+
declare function flattenComponents(components: ComponentsMap | null, prefix?: string | null): Record<string, ElementType> | null;
|
|
231
|
+
|
|
232
|
+
declare function formatDuration(timeInSeconds: number | string): string;
|
|
233
|
+
|
|
204
234
|
declare function getColumnsWithFields(resource: Resource, columns: TableColumn[] | null): Column[];
|
|
205
235
|
|
|
206
236
|
interface ComponentResult {
|
|
@@ -239,6 +269,8 @@ interface FieldWithSubFields extends Field {
|
|
|
239
269
|
type?: string | null;
|
|
240
270
|
fields?: Field[] | null;
|
|
241
271
|
field?: Field | null;
|
|
272
|
+
settings?: Field[] | null;
|
|
273
|
+
itemsField?: Field | null;
|
|
242
274
|
listItems?: boolean;
|
|
243
275
|
}
|
|
244
276
|
declare function getFieldFromPath(path: string | string[], fields: Field[], fieldManager: FieldManager): FieldWithSubFields | null;
|
|
@@ -259,6 +291,16 @@ declare function loadPackage<T = unknown>(name: string, loader: () => Promise<T>
|
|
|
259
291
|
|
|
260
292
|
declare function mergeRefs<T>(...refs: Array<ForwardedRef<T> | null | undefined>): (value: T | null) => void;
|
|
261
293
|
|
|
294
|
+
/**
|
|
295
|
+
* Parse a date-only value as a local date, ignoring its timezone.
|
|
296
|
+
*
|
|
297
|
+
* A date stored at midnight represents a calendar day, not an instant: converting it to the
|
|
298
|
+
* browser timezone would show the previous day west of the server timezone (ex: a date saved as
|
|
299
|
+
* 2026-10-01 00:00 UTC is 2026-09-30 20:00 in Montreal). Returns null when the value has a time,
|
|
300
|
+
* so the caller can parse it as an instant.
|
|
301
|
+
*/
|
|
302
|
+
declare function parseDateOnly(value: string | null | undefined): Date | null;
|
|
303
|
+
|
|
262
304
|
declare function toggleSelectedItem(items: Item[] | null, item: Item, { multiple: multipleSelection }?: {
|
|
263
305
|
multiple?: boolean;
|
|
264
306
|
}): Item[] | null;
|
|
@@ -277,4 +319,4 @@ interface FieldWithValidation extends Field {
|
|
|
277
319
|
}
|
|
278
320
|
declare function validateFields(fields: FieldWithValidation[], value: Record<string, unknown> | null): boolean;
|
|
279
321
|
|
|
280
|
-
export { checkClickable, convertStyleToString, copyToClipboard, createNullableOnChange, createUseEvent, flattenComponents, formatDuration, getColumnsWithFields, getComponent, getComponentFromName, getDefinitionFromId, getDisplayName, getFieldByName, getFieldFromPath, getFileName, getPathValue, getSelectOptions, isMessage, loadPackage, mergeRefs, selectItems, setValue as setFieldValue, slug, toggleSelectedItem, unselectItems, validateFields };
|
|
322
|
+
export { checkClickable, convertStyleToString, copyToClipboard, createNullableOnChange, createUseEvent, flattenComponents, formatDuration, getColumnsWithFields, getComponent, getComponentFromName, getDefinitionFromId, getDisplayName, getFieldByName, getFieldFromPath, getFileName, getPathValue, getSelectOptions, isMessage, loadPackage, mergeRefs, parseDateOnly, selectItems, setValue as setFieldValue, slug, toggleSelectedItem, unselectItems, validateFields };
|
package/dist/utils.js
CHANGED
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
import isNumber from 'lodash-es/isNumber';
|
|
2
|
-
import { snakeCase } from 'snake-case';
|
|
3
2
|
import isObject from 'lodash-es/isObject';
|
|
4
3
|
import { useEffect, isValidElement } from 'react';
|
|
5
4
|
import { isValidElementType } from 'react-is';
|
|
6
5
|
import isString from 'lodash-es/isString';
|
|
7
|
-
import { pascalCase, kebabCase, snakeCase
|
|
6
|
+
import { pascalCase, kebabCase, snakeCase } from 'change-case';
|
|
8
7
|
export { kebabCase as dashCase, pascalCase, snakeCase } from 'change-case';
|
|
9
8
|
import isFunction from 'lodash-es/isFunction';
|
|
10
9
|
import isArray from 'lodash-es/isArray';
|
|
@@ -44,8 +43,17 @@ function checkClickable(el, options = {}, parentDistance = 1) {
|
|
|
44
43
|
return false;
|
|
45
44
|
}
|
|
46
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
|
+
}
|
|
47
55
|
function convertStyleToString(style) {
|
|
48
|
-
return style !== null ? Object.keys(style).map(key => `${
|
|
56
|
+
return style !== null ? Object.keys(style).map(key => `${toCssProperty(key)}:${isNumber(style[key]) ? `${style[key]}px` : style[key]};`).join('\n') : '';
|
|
49
57
|
}
|
|
50
58
|
|
|
51
59
|
function createNullableOnChange(onChange = null) {
|
|
@@ -142,12 +150,15 @@ function flattenComponents(components, prefix = null) {
|
|
|
142
150
|
if (components === null) {
|
|
143
151
|
return null;
|
|
144
152
|
}
|
|
145
|
-
return Object.keys(components).reduce((newMap, key) =>
|
|
146
|
-
|
|
147
|
-
[
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
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
|
+
};
|
|
151
162
|
}, {});
|
|
152
163
|
}
|
|
153
164
|
|
|
@@ -275,6 +286,9 @@ function getFieldByName(fields, name) {
|
|
|
275
286
|
}
|
|
276
287
|
|
|
277
288
|
function getFieldFromPath(path, fields, fieldManager) {
|
|
289
|
+
const initialField = {
|
|
290
|
+
fields
|
|
291
|
+
};
|
|
278
292
|
return (isArray(path) ? path : [path]).reduce((foundField, key) => {
|
|
279
293
|
if (foundField === null) {
|
|
280
294
|
return null;
|
|
@@ -298,9 +312,7 @@ function getFieldFromPath(path, fields, fieldManager) {
|
|
|
298
312
|
};
|
|
299
313
|
}
|
|
300
314
|
return getFieldByName([...(fieldFields || []), ...(subFields || []), ...(settings || [])], key);
|
|
301
|
-
},
|
|
302
|
-
fields
|
|
303
|
-
});
|
|
315
|
+
}, initialField);
|
|
304
316
|
}
|
|
305
317
|
|
|
306
318
|
function getFileName(url = null) {
|
|
@@ -359,24 +371,24 @@ function loadPackage(name, loader) {
|
|
|
359
371
|
loaders[name].resource = response;
|
|
360
372
|
events.emit(`loaded:${name}`, response);
|
|
361
373
|
return response;
|
|
374
|
+
}, error => {
|
|
375
|
+
// Remove the failed loader so a later call can retry
|
|
376
|
+
delete loaders[name];
|
|
377
|
+
throw error;
|
|
362
378
|
}),
|
|
363
379
|
loaded: false,
|
|
364
380
|
resource: null
|
|
365
381
|
};
|
|
366
382
|
}
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
resolve(response);
|
|
377
|
-
});
|
|
378
|
-
}
|
|
379
|
-
});
|
|
383
|
+
const {
|
|
384
|
+
loaded = false,
|
|
385
|
+
resource = null,
|
|
386
|
+
loader: currentLoader = null
|
|
387
|
+
} = loaders[name];
|
|
388
|
+
if (loaded === true) {
|
|
389
|
+
return Promise.resolve(resource);
|
|
390
|
+
}
|
|
391
|
+
return currentLoader;
|
|
380
392
|
}
|
|
381
393
|
|
|
382
394
|
function mergeRefs(...refs) {
|
|
@@ -391,6 +403,30 @@ function mergeRefs(...refs) {
|
|
|
391
403
|
};
|
|
392
404
|
}
|
|
393
405
|
|
|
406
|
+
// A date without time (2026-10-01), or a date at midnight with or without a timezone
|
|
407
|
+
// (2026-10-01T00:00:00-04:00, 2026-10-01T00:00:00.000000Z, 2026-10-01 00:00:00)
|
|
408
|
+
const DATE_ONLY_PATTERN = /^(\d{4})-(\d{2})-(\d{2})(?:[T ]00:00(?::00(?:\.0+)?)?(?:Z|[+-]\d{2}:?\d{2})?)?$/;
|
|
409
|
+
|
|
410
|
+
/**
|
|
411
|
+
* Parse a date-only value as a local date, ignoring its timezone.
|
|
412
|
+
*
|
|
413
|
+
* A date stored at midnight represents a calendar day, not an instant: converting it to the
|
|
414
|
+
* browser timezone would show the previous day west of the server timezone (ex: a date saved as
|
|
415
|
+
* 2026-10-01 00:00 UTC is 2026-09-30 20:00 in Montreal). Returns null when the value has a time,
|
|
416
|
+
* so the caller can parse it as an instant.
|
|
417
|
+
*/
|
|
418
|
+
function parseDateOnly(value) {
|
|
419
|
+
if (typeof value !== 'string') {
|
|
420
|
+
return null;
|
|
421
|
+
}
|
|
422
|
+
const match = DATE_ONLY_PATTERN.exec(value.trim());
|
|
423
|
+
if (match === null) {
|
|
424
|
+
return null;
|
|
425
|
+
}
|
|
426
|
+
const [, year, month, day] = match;
|
|
427
|
+
return new Date(parseInt(year, 10), parseInt(month, 10) - 1, parseInt(day, 10));
|
|
428
|
+
}
|
|
429
|
+
|
|
394
430
|
function toggleSelectedItem(items, item, {
|
|
395
431
|
multiple: multipleSelection = false
|
|
396
432
|
} = {}) {
|
|
@@ -441,8 +477,18 @@ function setValue(value, keyParts, fieldValue) {
|
|
|
441
477
|
if (value !== null || fieldValue !== null) {
|
|
442
478
|
if (isArray) {
|
|
443
479
|
const index = parseInt(key, 10);
|
|
444
|
-
const arrayValue = value;
|
|
445
|
-
|
|
480
|
+
const arrayValue = Array.isArray(value) ? value : [];
|
|
481
|
+
let newArrayValue;
|
|
482
|
+
if (fieldValue !== null) {
|
|
483
|
+
newArrayValue = [...arrayValue];
|
|
484
|
+
// Pad with null so an out-of-range index lands at the right position
|
|
485
|
+
while (newArrayValue.length < index) {
|
|
486
|
+
newArrayValue.push(null);
|
|
487
|
+
}
|
|
488
|
+
newArrayValue[index] = keyParts.length > 0 ? setValue(arrayValue[index] || null, keyParts, fieldValue) : fieldValue;
|
|
489
|
+
} else {
|
|
490
|
+
newArrayValue = [...arrayValue.slice(0, index), ...arrayValue.slice(index + 1)];
|
|
491
|
+
}
|
|
446
492
|
return newArrayValue.length > 0 ? newArrayValue : null;
|
|
447
493
|
}
|
|
448
494
|
const objectValue = value;
|
|
@@ -459,7 +505,7 @@ function slug(str, separator = null) {
|
|
|
459
505
|
if (separator === '-') {
|
|
460
506
|
toSlug = kebabCase(str);
|
|
461
507
|
} else {
|
|
462
|
-
toSlug = snakeCase
|
|
508
|
+
toSlug = snakeCase(str);
|
|
463
509
|
}
|
|
464
510
|
return slugify(toSlug, {
|
|
465
511
|
lower: true
|
|
@@ -472,11 +518,13 @@ function validateFields(fields, value) {
|
|
|
472
518
|
if (field.type === 'fields' && field.fields) {
|
|
473
519
|
return validateFields(field.fields, value);
|
|
474
520
|
}
|
|
475
|
-
const val = value && field.name
|
|
476
|
-
|
|
521
|
+
const val = value && field.name ? value[field.name] : null;
|
|
522
|
+
// Only null, undefined and empty strings are considered missing (0 and false are valid)
|
|
523
|
+
const isMissing = val === null || typeof val === 'undefined' || val === '';
|
|
524
|
+
return !(field.required && isMissing);
|
|
477
525
|
}
|
|
478
526
|
return acc;
|
|
479
527
|
}, true);
|
|
480
528
|
}
|
|
481
529
|
|
|
482
|
-
export { checkClickable, convertStyleToString, copyToClipboard, createNullableOnChange, createUseEvent, flattenComponents, formatDuration, getColumnsWithFields, getComponent, getComponentFromName, getDefinitionFromId, getDisplayName, getFieldByName, getFieldFromPath, getFileName, getPathValue, getSelectOptions, isMessage, loadPackage, mergeRefs, selectItems, setValue as setFieldValue, slug, toggleSelectedItem, unselectItems, validateFields };
|
|
530
|
+
export { checkClickable, convertStyleToString, copyToClipboard, createNullableOnChange, createUseEvent, flattenComponents, formatDuration, getColumnsWithFields, getComponent, getComponentFromName, getDefinitionFromId, getDisplayName, getFieldByName, getFieldFromPath, getFileName, getPathValue, getSelectOptions, isMessage, loadPackage, mergeRefs, parseDateOnly, selectItems, setValue as setFieldValue, slug, toggleSelectedItem, unselectItems, validateFields };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@panneau/core",
|
|
3
|
-
"version": "4.0
|
|
3
|
+
"version": "4.1.0",
|
|
4
4
|
"description": "",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"javascript"
|
|
@@ -75,7 +75,7 @@
|
|
|
75
75
|
"debug": "^4.3.4",
|
|
76
76
|
"flat": "^6.0.1",
|
|
77
77
|
"hoist-non-react-statics": "^3.3.2",
|
|
78
|
-
"lodash": "^4.17.21",
|
|
78
|
+
"lodash-es": "^4.17.21",
|
|
79
79
|
"react-intl": "^6.0.0 || ^7.0.0 || ^8.0.0 || ^10.0.0",
|
|
80
80
|
"react-is": "^19.2.5",
|
|
81
81
|
"slugify": "^1.5.0",
|
|
@@ -96,5 +96,5 @@
|
|
|
96
96
|
"publishConfig": {
|
|
97
97
|
"access": "public"
|
|
98
98
|
},
|
|
99
|
-
"gitHead": "
|
|
99
|
+
"gitHead": "8a6accc1aab59368bf546c83b9433dfe94ccf767"
|
|
100
100
|
}
|