@adobe-commerce/elsie 1.5.1-alpha003 → 1.6.0-alpha1
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/bin/builders/serve/index.js +3 -1
- package/config/jest.js +3 -3
- package/config/vite.mjs +13 -8
- package/package.json +3 -3
- package/src/components/Button/Button.tsx +2 -0
- package/src/components/Incrementer/Incrementer.css +6 -0
- package/src/components/Incrementer/Incrementer.stories.tsx +18 -0
- package/src/components/Incrementer/Incrementer.tsx +66 -59
- package/src/components/MultiSelect/MultiSelect.css +273 -0
- package/src/components/MultiSelect/MultiSelect.stories.tsx +457 -0
- package/src/components/MultiSelect/MultiSelect.tsx +763 -0
- package/src/components/MultiSelect/index.ts +11 -0
- package/src/components/Picker/Picker.tsx +5 -3
- package/src/components/ProductItemCard/ProductItemCard.css +1 -39
- package/src/components/ProductItemCard/ProductItemCard.stories.tsx +7 -10
- package/src/components/ProductItemCard/ProductItemCard.tsx +4 -21
- package/src/components/Table/Table.css +110 -0
- package/src/components/Table/Table.stories.tsx +761 -0
- package/src/components/Table/Table.tsx +249 -0
- package/src/components/Table/index.ts +11 -0
- package/src/components/TextSwatch/TextSwatch.tsx +5 -2
- package/src/components/ToggleButton/ToggleButton.css +13 -1
- package/src/components/ToggleButton/ToggleButton.stories.tsx +13 -6
- package/src/components/ToggleButton/ToggleButton.tsx +4 -0
- package/src/components/index.ts +5 -3
- package/src/docs/API/render.mdx +16 -0
- package/src/docs/slots.mdx +9 -1
- package/src/i18n/en_US.json +38 -0
- package/src/lib/aem/configs.ts +7 -4
- package/src/lib/render.tsx +21 -7
- package/src/lib/slot.tsx +99 -30
package/src/lib/slot.tsx
CHANGED
|
@@ -2,30 +2,35 @@
|
|
|
2
2
|
* Copyright 2024 Adobe
|
|
3
3
|
* All Rights Reserved.
|
|
4
4
|
*
|
|
5
|
-
* NOTICE: Adobe permits you to use, modify, and distribute this
|
|
6
|
-
* file in accordance with the terms of the Adobe license agreement
|
|
7
|
-
* accompanying it.
|
|
5
|
+
* NOTICE: Adobe permits you to use, modify, and distribute this
|
|
6
|
+
* file in accordance with the terms of the Adobe license agreement
|
|
7
|
+
* accompanying it.
|
|
8
8
|
*******************************************************************/
|
|
9
9
|
|
|
10
|
-
import {
|
|
10
|
+
import { IntlContext, Lang } from '@adobe-commerce/elsie/i18n';
|
|
11
|
+
import {
|
|
12
|
+
cloneElement,
|
|
13
|
+
ComponentChildren,
|
|
14
|
+
createElement,
|
|
15
|
+
RefObject,
|
|
16
|
+
VNode,
|
|
17
|
+
} from 'preact';
|
|
18
|
+
import { Children, HTMLAttributes, isValidElement } from 'preact/compat';
|
|
11
19
|
import {
|
|
12
20
|
StateUpdater,
|
|
21
|
+
useCallback,
|
|
13
22
|
useContext,
|
|
14
|
-
useState,
|
|
15
|
-
useRef,
|
|
16
23
|
useEffect,
|
|
17
24
|
useMemo,
|
|
18
|
-
|
|
25
|
+
useRef,
|
|
26
|
+
useState,
|
|
19
27
|
} from 'preact/hooks';
|
|
20
|
-
import { IntlContext, Lang } from '@adobe-commerce/elsie/i18n';
|
|
21
|
-
import { HTMLAttributes } from 'preact/compat';
|
|
22
28
|
import { SlotQueueContext } from './render';
|
|
23
29
|
|
|
24
30
|
import '@adobe-commerce/elsie/components/UIProvider/debugger.css';
|
|
25
31
|
|
|
26
32
|
type MutateElement = (elem: HTMLElement) => void;
|
|
27
33
|
|
|
28
|
-
|
|
29
34
|
interface State {
|
|
30
35
|
get: (key: string) => void;
|
|
31
36
|
set: (key: string, value: any) => void;
|
|
@@ -36,6 +41,7 @@ interface SlotElement {
|
|
|
36
41
|
prependChild: MutateElement;
|
|
37
42
|
appendSibling: MutateElement;
|
|
38
43
|
prependSibling: MutateElement;
|
|
44
|
+
remove: () => void;
|
|
39
45
|
}
|
|
40
46
|
|
|
41
47
|
interface PrivateContext<T> {
|
|
@@ -55,6 +61,7 @@ interface DefaultSlotContext<T> extends PrivateContext<T> {
|
|
|
55
61
|
prependChild: MutateElement;
|
|
56
62
|
appendSibling: MutateElement;
|
|
57
63
|
prependSibling: MutateElement;
|
|
64
|
+
remove: () => void;
|
|
58
65
|
onRender: (cb: (next: T & DefaultSlotContext<T>) => void) => void;
|
|
59
66
|
onChange: (cb: (next: T & DefaultSlotContext<T>) => void) => void;
|
|
60
67
|
}
|
|
@@ -80,7 +87,7 @@ export function useSlot<K, V extends HTMLElement>(
|
|
|
80
87
|
render?: Function,
|
|
81
88
|
// eslint-disable-next-line no-undef
|
|
82
89
|
contentTag: keyof HTMLElementTagNameMap = 'div'
|
|
83
|
-
): [RefObject<V>, Record<string, any
|
|
90
|
+
): [RefObject<V>, Record<string, any>, 'loading' | 'pending' | 'ready'] {
|
|
84
91
|
const slotsQueue = useContext(SlotQueueContext);
|
|
85
92
|
|
|
86
93
|
// HTML Element
|
|
@@ -149,18 +156,21 @@ export function useSlot<K, V extends HTMLElement>(
|
|
|
149
156
|
// @ts-ignore
|
|
150
157
|
context._registerMethod = _registerMethod;
|
|
151
158
|
|
|
152
|
-
const _htmlElementToVNode = useCallback(
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
refElem
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
159
|
+
const _htmlElementToVNode = useCallback(
|
|
160
|
+
(elem: HTMLElement) => {
|
|
161
|
+
return createElement(
|
|
162
|
+
contentTag,
|
|
163
|
+
{
|
|
164
|
+
'data-slot-html-element': elem.tagName.toLowerCase(),
|
|
165
|
+
ref: (refElem: HTMLElement | null): void => {
|
|
166
|
+
refElem?.appendChild(elem);
|
|
167
|
+
},
|
|
168
|
+
},
|
|
169
|
+
null
|
|
170
|
+
);
|
|
171
|
+
},
|
|
172
|
+
[contentTag]
|
|
173
|
+
);
|
|
164
174
|
|
|
165
175
|
// @ts-ignore
|
|
166
176
|
context._htmlElementToVNode = _htmlElementToVNode;
|
|
@@ -199,6 +209,10 @@ export function useSlot<K, V extends HTMLElement>(
|
|
|
199
209
|
const parent = element.parentNode;
|
|
200
210
|
parent?.insertBefore(elem, element);
|
|
201
211
|
},
|
|
212
|
+
|
|
213
|
+
remove: () => {
|
|
214
|
+
element.remove();
|
|
215
|
+
},
|
|
202
216
|
};
|
|
203
217
|
},
|
|
204
218
|
[name]
|
|
@@ -293,6 +307,14 @@ export function useSlot<K, V extends HTMLElement>(
|
|
|
293
307
|
[_registerMethod]
|
|
294
308
|
);
|
|
295
309
|
|
|
310
|
+
// @ts-ignore
|
|
311
|
+
context.remove = useCallback(() => {
|
|
312
|
+
// @ts-ignore
|
|
313
|
+
_registerMethod(() => {
|
|
314
|
+
elementRef.current?.remove();
|
|
315
|
+
});
|
|
316
|
+
}, [_registerMethod]);
|
|
317
|
+
|
|
296
318
|
const handleLifeCycleRender = useCallback(async () => {
|
|
297
319
|
if (status.current === 'loading') return;
|
|
298
320
|
|
|
@@ -322,7 +344,10 @@ export function useSlot<K, V extends HTMLElement>(
|
|
|
322
344
|
status.current = 'loading';
|
|
323
345
|
|
|
324
346
|
log(`🟩 "${name}" Slot Initialized`);
|
|
325
|
-
await callback(
|
|
347
|
+
await callback(
|
|
348
|
+
context as K & DefaultSlotContext<K>,
|
|
349
|
+
elementRef.current as HTMLDivElement | null
|
|
350
|
+
);
|
|
326
351
|
} catch (error) {
|
|
327
352
|
console.error(`Error in "${callback.name}" Slot callback`, error);
|
|
328
353
|
} finally {
|
|
@@ -336,7 +361,7 @@ export function useSlot<K, V extends HTMLElement>(
|
|
|
336
361
|
// Initialization
|
|
337
362
|
useEffect(() => {
|
|
338
363
|
handleLifeCycleInit().finally(() => {
|
|
339
|
-
if (slotsQueue) {
|
|
364
|
+
if (slotsQueue && slotsQueue.value.has(name)) {
|
|
340
365
|
slotsQueue.value.delete(name);
|
|
341
366
|
slotsQueue.value = new Set(slotsQueue.value);
|
|
342
367
|
}
|
|
@@ -352,13 +377,56 @@ export function useSlot<K, V extends HTMLElement>(
|
|
|
352
377
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
353
378
|
}, [JSON.stringify(context), JSON.stringify(_state)]);
|
|
354
379
|
|
|
355
|
-
return [elementRef, props];
|
|
380
|
+
return [elementRef, props, status.current];
|
|
356
381
|
}
|
|
357
382
|
|
|
383
|
+
/**
|
|
384
|
+
* Recursively processes children elements to conditionally prevent image loading.
|
|
385
|
+
*
|
|
386
|
+
* This function traverses the children tree and modifies img elements to prevent
|
|
387
|
+
* premature loading during slot initialization. When isReady is false, img src
|
|
388
|
+
* attributes are set to empty string to prevent network requests, while preserving
|
|
389
|
+
* the original src in a data attribute for debugging purposes.
|
|
390
|
+
*
|
|
391
|
+
* @param children - The children elements to process (can be any React/Preact children)
|
|
392
|
+
* @param isReady - Whether the slot is ready to load images (true) or should prevent loading (false)
|
|
393
|
+
* @returns Processed children with conditional image src attributes
|
|
394
|
+
*/
|
|
395
|
+
const processChildren = (children: any, isReady: boolean): any => {
|
|
396
|
+
return Children.map(children, child => {
|
|
397
|
+
// Handle text nodes, numbers, etc.
|
|
398
|
+
if (!isValidElement(child)) {
|
|
399
|
+
return child;
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
// Handle img elements - conditionally set src
|
|
403
|
+
if (child.props.src) {
|
|
404
|
+
return cloneElement(child, {
|
|
405
|
+
...child.props,
|
|
406
|
+
src: isReady ? child.props.src : "",
|
|
407
|
+
// Optionally preserve original src in data attribute for debugging
|
|
408
|
+
'data-original-src': child.props.src,
|
|
409
|
+
});
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
// Handle elements with children - recursively process them
|
|
413
|
+
if (child.props && child.props.children) {
|
|
414
|
+
return cloneElement(child, {
|
|
415
|
+
...child.props,
|
|
416
|
+
children: processChildren(child.props.children, isReady),
|
|
417
|
+
});
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
// Return other elements as-is
|
|
421
|
+
return child;
|
|
422
|
+
});
|
|
423
|
+
};
|
|
424
|
+
|
|
358
425
|
// Slot Component
|
|
359
426
|
interface SlotPropsComponent<T>
|
|
360
427
|
extends Omit<HTMLAttributes<HTMLElement>, 'slot'> {
|
|
361
428
|
name: string;
|
|
429
|
+
lazy?: boolean;
|
|
362
430
|
slot?: SlotProps<T>;
|
|
363
431
|
context?: Context<T>;
|
|
364
432
|
render?: (props: Record<string, any>) => VNode | VNode[];
|
|
@@ -371,6 +439,7 @@ interface SlotPropsComponent<T>
|
|
|
371
439
|
|
|
372
440
|
export function Slot<T>({
|
|
373
441
|
name,
|
|
442
|
+
lazy = false,
|
|
374
443
|
context,
|
|
375
444
|
slot,
|
|
376
445
|
children,
|
|
@@ -385,7 +454,7 @@ export function Slot<T>({
|
|
|
385
454
|
}> {
|
|
386
455
|
const slotsQueue = useContext(SlotQueueContext);
|
|
387
456
|
|
|
388
|
-
const [elementRef, slotProps] = useSlot<T, HTMLElement>(
|
|
457
|
+
const [elementRef, slotProps, status] = useSlot<T, HTMLElement>(
|
|
389
458
|
name,
|
|
390
459
|
context,
|
|
391
460
|
slot,
|
|
@@ -400,11 +469,11 @@ export function Slot<T>({
|
|
|
400
469
|
}
|
|
401
470
|
|
|
402
471
|
// add slot to queue
|
|
403
|
-
if (slotsQueue) {
|
|
472
|
+
if (slotsQueue && lazy === false) {
|
|
404
473
|
slotsQueue.value.add(name);
|
|
405
474
|
slotsQueue.value = new Set(slotsQueue.value);
|
|
406
475
|
}
|
|
407
|
-
}, [name, slotsQueue]);
|
|
476
|
+
}, [name, lazy, slotsQueue]);
|
|
408
477
|
|
|
409
478
|
return createElement(
|
|
410
479
|
slotTag,
|
|
@@ -413,7 +482,7 @@ export function Slot<T>({
|
|
|
413
482
|
ref: elementRef,
|
|
414
483
|
'data-slot': name,
|
|
415
484
|
},
|
|
416
|
-
slotProps.children
|
|
485
|
+
processChildren(slotProps.children, status === 'ready')
|
|
417
486
|
);
|
|
418
487
|
}
|
|
419
488
|
|