@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.
Files changed (31) hide show
  1. package/bin/builders/serve/index.js +3 -1
  2. package/config/jest.js +3 -3
  3. package/config/vite.mjs +13 -8
  4. package/package.json +3 -3
  5. package/src/components/Button/Button.tsx +2 -0
  6. package/src/components/Incrementer/Incrementer.css +6 -0
  7. package/src/components/Incrementer/Incrementer.stories.tsx +18 -0
  8. package/src/components/Incrementer/Incrementer.tsx +66 -59
  9. package/src/components/MultiSelect/MultiSelect.css +273 -0
  10. package/src/components/MultiSelect/MultiSelect.stories.tsx +457 -0
  11. package/src/components/MultiSelect/MultiSelect.tsx +763 -0
  12. package/src/components/MultiSelect/index.ts +11 -0
  13. package/src/components/Picker/Picker.tsx +5 -3
  14. package/src/components/ProductItemCard/ProductItemCard.css +1 -39
  15. package/src/components/ProductItemCard/ProductItemCard.stories.tsx +7 -10
  16. package/src/components/ProductItemCard/ProductItemCard.tsx +4 -21
  17. package/src/components/Table/Table.css +110 -0
  18. package/src/components/Table/Table.stories.tsx +761 -0
  19. package/src/components/Table/Table.tsx +249 -0
  20. package/src/components/Table/index.ts +11 -0
  21. package/src/components/TextSwatch/TextSwatch.tsx +5 -2
  22. package/src/components/ToggleButton/ToggleButton.css +13 -1
  23. package/src/components/ToggleButton/ToggleButton.stories.tsx +13 -6
  24. package/src/components/ToggleButton/ToggleButton.tsx +4 -0
  25. package/src/components/index.ts +5 -3
  26. package/src/docs/API/render.mdx +16 -0
  27. package/src/docs/slots.mdx +9 -1
  28. package/src/i18n/en_US.json +38 -0
  29. package/src/lib/aem/configs.ts +7 -4
  30. package/src/lib/render.tsx +21 -7
  31. 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 { cloneElement, ComponentChildren, RefObject, VNode, createElement } from 'preact';
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
- useCallback,
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((elem: HTMLElement) => {
153
- return createElement(
154
- contentTag,
155
- {
156
- 'data-slot-html-element': elem.tagName.toLowerCase(),
157
- ref: (refElem: HTMLElement | null): void => {
158
- refElem?.appendChild(elem);
159
- }
160
- },
161
- null
162
- );
163
- }, [contentTag]);
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(context as K & DefaultSlotContext<K>, elementRef.current as HTMLDivElement | null);
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