@adobe-commerce/elsie 1.5.0-alpha2 → 1.5.0-alpha2903

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.
@@ -0,0 +1,249 @@
1
+ /********************************************************************
2
+ * Copyright 2025 Adobe
3
+ * All Rights Reserved.
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.
8
+ *******************************************************************/
9
+
10
+ import { FunctionComponent, VNode, Fragment } from 'preact';
11
+ import { HTMLAttributes } from 'preact/compat';
12
+ import { classes, VComponent } from '@adobe-commerce/elsie/lib';
13
+ import {
14
+ Icon,
15
+ Button,
16
+ Skeleton,
17
+ SkeletonRow,
18
+ } from '@adobe-commerce/elsie/components';
19
+ import { useText } from '@adobe-commerce/elsie/i18n';
20
+
21
+ import '@adobe-commerce/elsie/components/Table/Table.css';
22
+
23
+ type Sortable = 'asc' | 'desc' | true;
24
+
25
+ type Column =
26
+ | { label: string; key: string; ariaLabel?: string; sortBy?: Sortable }
27
+ | {
28
+ label: VNode<HTMLAttributes<HTMLElement>>;
29
+ key: string;
30
+ ariaLabel: string;
31
+ sortBy?: Sortable;
32
+ };
33
+
34
+ type RowData = {
35
+ [key: string]: VNode | string | number | undefined;
36
+ _rowDetails?: VNode | string; // Special property for expandable row content
37
+ };
38
+
39
+ export interface TableProps
40
+ extends Omit<HTMLAttributes<HTMLTableElement>, 'loading'> {
41
+ columns: Column[];
42
+ rowData: RowData[];
43
+ mobileLayout?: 'stacked' | 'none';
44
+ caption?: string;
45
+ expandedRows?: Set<number>;
46
+ loading?: boolean;
47
+ skeletonRowCount?: number;
48
+ onSortChange?: (columnKey: string, direction: Sortable) => void;
49
+ }
50
+
51
+ export const Table: FunctionComponent<TableProps> = ({
52
+ className,
53
+ children,
54
+ columns = [],
55
+ rowData = [],
56
+ mobileLayout = 'none',
57
+ caption,
58
+ expandedRows = new Set(),
59
+ loading = false,
60
+ skeletonRowCount = 10,
61
+ onSortChange,
62
+ ...props
63
+ }) => {
64
+ const translations = useText({
65
+ sortedAscending: 'Dropin.Table.sortedAscending',
66
+ sortedDescending: 'Dropin.Table.sortedDescending',
67
+ sortBy: 'Dropin.Table.sortBy',
68
+ });
69
+
70
+ const handleSort = (column: Column) => {
71
+ if (!onSortChange) return;
72
+
73
+ // Determine next sort direction
74
+ let nextDirection: Sortable;
75
+ if (column.sortBy === true) {
76
+ nextDirection = 'asc';
77
+ } else if (column.sortBy === 'asc') {
78
+ nextDirection = 'desc';
79
+ } else {
80
+ nextDirection = true;
81
+ }
82
+
83
+ onSortChange(column.key, nextDirection);
84
+ };
85
+
86
+ const renderSortButton = (column: Column) => {
87
+ if (column.sortBy === undefined) return null;
88
+ const label = column.ariaLabel ?? (column.label as string);
89
+
90
+ let iconSource: string;
91
+ let ariaLabel: string;
92
+
93
+ if (column.sortBy === 'asc') {
94
+ iconSource = 'ChevronUp';
95
+ ariaLabel = translations.sortedAscending.replace('{label}', label);
96
+ } else if (column.sortBy === 'desc') {
97
+ iconSource = 'ChevronDown';
98
+ ariaLabel = translations.sortedDescending.replace('{label}', label);
99
+ } else {
100
+ iconSource = 'Sort';
101
+ ariaLabel = translations.sortBy.replace('{label}', label);
102
+ }
103
+
104
+ return (
105
+ <Button
106
+ variant="tertiary"
107
+ size="medium"
108
+ className="dropin-table__header__sort-button"
109
+ icon={<Icon source={iconSource} />}
110
+ aria-label={ariaLabel}
111
+ onClick={() => handleSort(column)}
112
+ />
113
+ );
114
+ };
115
+
116
+ const renderSkeletonRows = () => {
117
+ return Array.from({ length: skeletonRowCount }, (_, rowIndex) => (
118
+ <tr key={`skeleton-${rowIndex}`} className="dropin-table__body__row">
119
+ {columns.map((column) => (
120
+ <td
121
+ key={column.key}
122
+ className="dropin-table__body__cell"
123
+ data-label={column.ariaLabel ?? column.label}
124
+ >
125
+ <Skeleton>
126
+ <SkeletonRow variant="row" size="small" fullWidth />
127
+ </Skeleton>
128
+ </td>
129
+ ))}
130
+ </tr>
131
+ ));
132
+ };
133
+
134
+ const renderDataRows = () => {
135
+ return rowData.map((row, rowIndex) => {
136
+ const hasDetails = row._rowDetails !== undefined;
137
+ const isExpanded = expandedRows.has(rowIndex);
138
+
139
+ return (
140
+ <Fragment key={rowIndex}>
141
+ <tr className="dropin-table__body__row">
142
+ {columns.map((column) => {
143
+ const cell = row[column.key];
144
+ const label = column.ariaLabel ?? column.label;
145
+
146
+ if (typeof cell === 'string' || typeof cell === 'number') {
147
+ return (
148
+ <td
149
+ key={column.key}
150
+ className="dropin-table__body__cell"
151
+ data-label={label}
152
+ >
153
+ {cell}
154
+ </td>
155
+ );
156
+ }
157
+
158
+ return (
159
+ <td
160
+ key={column.key}
161
+ className="dropin-table__body__cell"
162
+ data-label={label}
163
+ >
164
+ <VComponent node={cell!} />
165
+ </td>
166
+ );
167
+ })}
168
+ </tr>
169
+ {hasDetails && isExpanded && (
170
+ <tr
171
+ key={`${rowIndex}-details`}
172
+ className="dropin-table__row-details dropin-table__row-details--expanded"
173
+ id={`row-${rowIndex}-details`}
174
+ >
175
+ <td
176
+ className="dropin-table__row-details__cell"
177
+ colSpan={columns.length}
178
+ role="region"
179
+ aria-labelledby={`row-${rowIndex}-details`}
180
+ >
181
+ {typeof row._rowDetails === 'string' ? (
182
+ row._rowDetails
183
+ ) : (
184
+ <VComponent node={row._rowDetails!} />
185
+ )}
186
+ </td>
187
+ </tr>
188
+ )}
189
+ </Fragment>
190
+ );
191
+ });
192
+ };
193
+
194
+ const getAriaSort = (
195
+ column: Column
196
+ ): 'none' | 'ascending' | 'descending' | 'other' | undefined => {
197
+ if (column.sortBy === true) return 'none';
198
+ if (column.sortBy === 'asc') return 'ascending';
199
+ if (column.sortBy === 'desc') return 'descending';
200
+ return undefined;
201
+ };
202
+
203
+ return (
204
+ <div
205
+ className={classes([
206
+ 'dropin-table',
207
+ `dropin-table--mobile-layout-${mobileLayout}`,
208
+ className,
209
+ ])}
210
+ >
211
+ <table {...props} className="dropin-table__table">
212
+ {caption && (
213
+ <caption className="dropin-table__caption">{caption}</caption>
214
+ )}
215
+ <thead className="dropin-table__header">
216
+ <tr className="dropin-table__header__row">
217
+ {columns.map((column) => (
218
+ <th
219
+ key={column.key}
220
+ className={classes([
221
+ 'dropin-table__header__cell',
222
+ [
223
+ 'dropin-table__header__cell--sorted',
224
+ column.sortBy === 'asc' || column.sortBy === 'desc',
225
+ ],
226
+ [
227
+ 'dropin-table__header__cell--sortable',
228
+ column.sortBy !== undefined,
229
+ ],
230
+ ])}
231
+ aria-sort={getAriaSort(column)}
232
+ >
233
+ {column.label}
234
+ {renderSortButton(column)}
235
+ </th>
236
+ ))}
237
+ </tr>
238
+ </thead>
239
+ <tbody className="dropin-table__body">
240
+ {loading
241
+ ? // Render skeleton rows when loading
242
+ renderSkeletonRows()
243
+ : // Render actual data when not loading
244
+ renderDataRows()}
245
+ </tbody>
246
+ </table>
247
+ </div>
248
+ );
249
+ };
@@ -0,0 +1,11 @@
1
+ /********************************************************************
2
+ * Copyright 2025 Adobe
3
+ * All Rights Reserved.
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.
8
+ *******************************************************************/
9
+
10
+ export * from '@adobe-commerce/elsie/components/Table/Table';
11
+ export { Table as default } from '@adobe-commerce/elsie/components/Table/Table';
@@ -34,7 +34,19 @@
34
34
  border: var(--shape-border-width-1) solid var(--color-neutral-800);
35
35
  }
36
36
 
37
- .dropin-toggle-button:has(input:focus-visible) {
37
+ /* Disabled */
38
+ .dropin-toggle-button__disabled .dropin-toggle-button__actionButton {
39
+ cursor: default;
40
+ background-color: var(--color-neutral-300);
41
+ border: var(--shape-border-width-1) solid var(--color-neutral-500);
42
+ }
43
+
44
+ .dropin-toggle-button__disabled .dropin-toggle-button__content {
45
+ color: var(--color-neutral-500);
46
+ cursor: default;
47
+ }
48
+
49
+ .dropin-toggle-button:not(.dropin-toggle-button__disabled):has(input:focus-visible) {
38
50
  outline: 0 none;
39
51
  box-shadow: 0 0 0 var(--shape-icon-stroke-4) var(--color-neutral-400);
40
52
  -webkit-box-shadow: 0 0 0 var(--shape-icon-stroke-4) var(--color-neutral-400);
@@ -79,6 +79,15 @@ const meta: Meta<ToggleButtonProps> = {
79
79
  type: 'boolean',
80
80
  },
81
81
  },
82
+ disabled: {
83
+ description: 'Whether or not the Toggle button is disabled',
84
+ type: {
85
+ name: 'boolean',
86
+ },
87
+ control: {
88
+ type: 'boolean',
89
+ },
90
+ },
82
91
  onChange: {
83
92
  description: 'Function to be called when the Toggle button is clicked',
84
93
  type: {
@@ -103,11 +112,9 @@ export const ToggleButtonStory: Story = {
103
112
  },
104
113
  play: async ({ canvasElement }) => {
105
114
  const canvas = within(canvasElement);
106
- const toggleButton = document.querySelector('.dropin-toggle-button');
107
115
  const toggleButtonInput = await canvas.findByRole('radio');
108
- const toggleButtonText = document.querySelector(
109
- '.dropin-toggle-button__content'
110
- );
116
+ const toggleButton = toggleButtonInput.closest('.dropin-toggle-button');
117
+ const toggleButtonText = toggleButton?.querySelector('.dropin-toggle-button__content');
111
118
  await expect(toggleButton).toHaveClass('dropin-toggle-button__selected');
112
119
  await expect(toggleButtonText).toHaveTextContent('Toggle Button label');
113
120
  await expect(toggleButtonInput).toBeChecked();
@@ -124,9 +131,9 @@ export const ToggleButtonNotSelected: Story = {
124
131
  },
125
132
  play: async ({ canvasElement }) => {
126
133
  const canvas = within(canvasElement);
127
- const toggleButton = document.querySelector('.dropin-toggle-button');
128
134
  const toggleButtonInput = await canvas.findByRole('radio');
129
- const toggleButtonText = await canvas.findByText('Toggle Button label');
135
+ const toggleButton = toggleButtonInput.closest('.dropin-toggle-button');
136
+ const toggleButtonText = toggleButton?.querySelector('.dropin-toggle-button__content');
130
137
  await expect(toggleButton).not.toHaveClass(
131
138
  'dropin-toggle-button__selected'
132
139
  );
@@ -19,6 +19,7 @@ export interface ToggleButtonProps
19
19
  name: string;
20
20
  value: string;
21
21
  busy?: boolean;
22
+ disabled?: boolean;
22
23
  icon?:
23
24
  | VNode<HTMLAttributes<SVGSVGElement>>
24
25
  | VNode<HTMLAttributes<HTMLImageElement>>;
@@ -31,6 +32,7 @@ export const ToggleButton: FunctionComponent<ToggleButtonProps> = ({
31
32
  name,
32
33
  value,
33
34
  busy = false,
35
+ disabled = false,
34
36
  children,
35
37
  className,
36
38
  icon,
@@ -45,6 +47,7 @@ export const ToggleButton: FunctionComponent<ToggleButtonProps> = ({
45
47
  'dropin-toggle-button',
46
48
  className,
47
49
  ['dropin-toggle-button__selected', selected],
50
+ ['dropin-toggle-button__disabled', disabled],
48
51
  ])}
49
52
  >
50
53
  <label className="dropin-toggle-button__actionButton">
@@ -53,6 +56,7 @@ export const ToggleButton: FunctionComponent<ToggleButtonProps> = ({
53
56
  name={name}
54
57
  value={value}
55
58
  checked={selected}
59
+ disabled={disabled}
56
60
  onChange={() => onChange && onChange(value)}
57
61
  aria-label={name}
58
62
  busy={busy}
@@ -49,3 +49,4 @@ export * from '@adobe-commerce/elsie/components/ContentGrid';
49
49
  export * from '@adobe-commerce/elsie/components/Pagination';
50
50
  export * from '@adobe-commerce/elsie/components/ProductItemCard';
51
51
  export * from '@adobe-commerce/elsie/components/InputFile';
52
+ export * from '@adobe-commerce/elsie/components/Table';
@@ -19,6 +19,7 @@ The context is defined during implementation of a drop-in and can be used to pas
19
19
  - **prependChild**: A function to prepend a new HTML element to the slot's content.
20
20
  - **appendSibling**: A function to append a new HTML element after the slot's content.
21
21
  - **prependSibling**: A function to prepend a new HTML element **before** the slot's content.
22
+ - **remove**: A function to remove the slot element from the DOM.
22
23
  - **getSlotElement**: A function to get a slot element.
23
24
  - **onChange**: A function to listen to changes in the slot's context.
24
25
 
@@ -32,6 +33,12 @@ The `<Slot />` component is used to define a slot in a container. It receives a
32
33
 
33
34
  The name of the slot in _PascalCase_. `string` (required).
34
35
 
36
+ ### lazy
37
+
38
+ Controls whether the slot should be loaded immediately or deferred for later initialization. `boolean` (optional).
39
+
40
+ When `lazy={false}`, the slot is initialized as soon as the container mounts. When `lazy={true}`, the slot can be initialized later on when it is needed. This is useful for performance optimization, especially when the slot's content is not immediately required.
41
+
35
42
  ### slotTag
36
43
 
37
44
  The HTML tag to use for the slot's wrapper element. This allows you to change the wrapper element from the default `div` to any valid HTML tag (e.g., 'span', 'p', 'a', etc.). When using specific tags like 'a', you can also provide their respective HTML attributes (e.g., 'href', 'target', etc.).
@@ -71,7 +78,7 @@ Example:
71
78
 
72
79
  - `ctx`: An object representing the context of the slot, including methods for manipulating the slot's content.
73
80
 
74
- The slot property, which is implemented as a promise function, provides developers with the flexibility to dynamically generate and manipulate content within slots.
81
+ The slot property, which is implemented as a promise function, provides developers with the flexibility to dynamically generate and manipulate content within slots.
75
82
  However, it's important to note that this promise is render-blocking, meaning that the component will not render until the promise is resolved.
76
83
 
77
84
  ### context
@@ -144,6 +151,7 @@ provider.render(MyContainer, {
144
151
  // ctx.prependChild(element);
145
152
  // ctx.appendSibling(element);
146
153
  // ctx.prependSibling(element);
154
+ // ctx.remove();
147
155
 
148
156
  // to listen and react to changes in the slot's context (lifecycle)
149
157
  ctx.onChange((next) => {
@@ -141,6 +141,11 @@
141
141
  },
142
142
  "InputDate": {
143
143
  "picker": "Select a date"
144
+ },
145
+ "Table": {
146
+ "sortedAscending": "Sort {label} ascending",
147
+ "sortedDescending": "Sort {label} descending",
148
+ "sortBy": "Sort by {label}"
144
149
  }
145
150
  }
146
151
  }
@@ -40,7 +40,7 @@ function resetConfig() {
40
40
  * @param {Object} [configObj=config] - The config object.
41
41
  * @returns {string} - The root path.
42
42
  */
43
- function getRootPath(configObj: Config | null = config): string {
43
+ function getRootPath(configObj: Config | null = config, options?: { match?: (key: string) => boolean }): string {
44
44
  if (!configObj) {
45
45
  console.warn('No config found. Please call initializeConfig() first.');
46
46
  return '/';
@@ -56,7 +56,7 @@ function getRootPath(configObj: Config | null = config): string {
56
56
  .find(
57
57
  (key) =>
58
58
  window.location.pathname === key ||
59
- window.location.pathname.startsWith(key)
59
+ (options?.match?.(key) ?? window.location.pathname.startsWith(key))
60
60
  );
61
61
 
62
62
  return value ?? '/';
@@ -126,11 +126,14 @@ function applyConfigOverrides(
126
126
 
127
127
  /**
128
128
  * Initializes the configuration system.
129
+ * @param {Object} configObj - The config object.
130
+ * @param {Object} [options] - The options object.
131
+ * @param {Function} [options.match] - The function to match the path to the config.
129
132
  * @returns {Object} The initialized root configuration
130
133
  */
131
- function initializeConfig(configObj: Config): ConfigRoot {
134
+ function initializeConfig(configObj: Config, options?: { match?: (key: string) => boolean }): ConfigRoot {
132
135
  config = configObj;
133
- rootPath = getRootPath(config);
136
+ rootPath = getRootPath(config, { match: options?.match });
134
137
  rootConfig = applyConfigOverrides(config, rootPath);
135
138
  return rootConfig;
136
139
  }
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 { HTMLAttributes } 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
  }
@@ -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
  }
@@ -359,6 +384,7 @@ export function useSlot<K, V extends HTMLElement>(
359
384
  interface SlotPropsComponent<T>
360
385
  extends Omit<HTMLAttributes<HTMLElement>, 'slot'> {
361
386
  name: string;
387
+ lazy?: boolean;
362
388
  slot?: SlotProps<T>;
363
389
  context?: Context<T>;
364
390
  render?: (props: Record<string, any>) => VNode | VNode[];
@@ -371,6 +397,7 @@ interface SlotPropsComponent<T>
371
397
 
372
398
  export function Slot<T>({
373
399
  name,
400
+ lazy = false,
374
401
  context,
375
402
  slot,
376
403
  children,
@@ -400,11 +427,11 @@ export function Slot<T>({
400
427
  }
401
428
 
402
429
  // add slot to queue
403
- if (slotsQueue) {
430
+ if (slotsQueue && lazy === false) {
404
431
  slotsQueue.value.add(name);
405
432
  slotsQueue.value = new Set(slotsQueue.value);
406
433
  }
407
- }, [name, slotsQueue]);
434
+ }, [name, lazy, slotsQueue]);
408
435
 
409
436
  return createElement(
410
437
  slotTag,