@adobe-commerce/elsie 1.5.0-beta2 → 1.5.0-beta4
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/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/Table/Table.css +110 -0
- package/src/components/Table/Table.stories.tsx +673 -0
- package/src/components/Table/Table.tsx +208 -0
- package/src/components/Table/index.ts +11 -0
- 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 +1 -0
- package/src/docs/slots.mdx +7 -1
- package/src/i18n/en_US.json +5 -0
- package/src/lib/slot.tsx +39 -26
|
@@ -0,0 +1,208 @@
|
|
|
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 { Icon, Button, Skeleton, SkeletonRow } from '@adobe-commerce/elsie/components';
|
|
14
|
+
import { useText } from '@adobe-commerce/elsie/i18n';
|
|
15
|
+
|
|
16
|
+
import '@adobe-commerce/elsie/components/Table/Table.css';
|
|
17
|
+
|
|
18
|
+
type Sortable = 'asc' | 'desc' | true;
|
|
19
|
+
|
|
20
|
+
type Column = {
|
|
21
|
+
key: string;
|
|
22
|
+
label: string;
|
|
23
|
+
sortBy?: Sortable;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
type RowData = {
|
|
27
|
+
[key: string]: VNode | string | number | undefined;
|
|
28
|
+
_rowDetails?: VNode | string; // Special property for expandable row content
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
export interface TableProps extends Omit<HTMLAttributes<HTMLTableElement>, 'loading'> {
|
|
32
|
+
columns: Column[];
|
|
33
|
+
rowData: RowData[];
|
|
34
|
+
mobileLayout?: 'stacked' | 'none';
|
|
35
|
+
caption?: string;
|
|
36
|
+
expandedRows?: Set<number>;
|
|
37
|
+
loading?: boolean;
|
|
38
|
+
skeletonRowCount?: number;
|
|
39
|
+
onSortChange?: (columnKey: string, direction: Sortable) => void;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export const Table: FunctionComponent<TableProps> = ({
|
|
43
|
+
className,
|
|
44
|
+
children,
|
|
45
|
+
columns = [],
|
|
46
|
+
rowData = [],
|
|
47
|
+
mobileLayout = 'none',
|
|
48
|
+
caption,
|
|
49
|
+
expandedRows = new Set(),
|
|
50
|
+
loading = false,
|
|
51
|
+
skeletonRowCount = 10,
|
|
52
|
+
onSortChange,
|
|
53
|
+
...props
|
|
54
|
+
}) => {
|
|
55
|
+
const translations = useText({
|
|
56
|
+
sortedAscending: 'Dropin.Table.sortedAscending',
|
|
57
|
+
sortedDescending: 'Dropin.Table.sortedDescending',
|
|
58
|
+
sortBy: 'Dropin.Table.sortBy',
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
const handleSort = (column: Column) => {
|
|
62
|
+
if (!onSortChange) return;
|
|
63
|
+
|
|
64
|
+
// Determine next sort direction
|
|
65
|
+
let nextDirection: Sortable;
|
|
66
|
+
if (column.sortBy === true) {
|
|
67
|
+
nextDirection = 'asc';
|
|
68
|
+
} else if (column.sortBy === 'asc') {
|
|
69
|
+
nextDirection = 'desc';
|
|
70
|
+
} else {
|
|
71
|
+
nextDirection = true;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
onSortChange(column.key, nextDirection);
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
const renderSortButton = (column: Column) => {
|
|
78
|
+
if (column.sortBy === undefined) return null;
|
|
79
|
+
|
|
80
|
+
let iconSource: string;
|
|
81
|
+
let ariaLabel: string;
|
|
82
|
+
|
|
83
|
+
if (column.sortBy === 'asc') {
|
|
84
|
+
iconSource = 'ChevronUp';
|
|
85
|
+
ariaLabel = translations.sortedAscending.replace('{label}', column.label);
|
|
86
|
+
} else if (column.sortBy === 'desc') {
|
|
87
|
+
iconSource = 'ChevronDown';
|
|
88
|
+
ariaLabel = translations.sortedDescending.replace('{label}', column.label);
|
|
89
|
+
} else {
|
|
90
|
+
iconSource = 'Sort';
|
|
91
|
+
ariaLabel = translations.sortBy.replace('{label}', column.label);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
return (
|
|
95
|
+
<Button
|
|
96
|
+
variant="tertiary"
|
|
97
|
+
size="medium"
|
|
98
|
+
className="dropin-table__header__sort-button"
|
|
99
|
+
icon={<Icon source={iconSource} />}
|
|
100
|
+
aria-label={ariaLabel}
|
|
101
|
+
onClick={() => handleSort(column)}
|
|
102
|
+
/>
|
|
103
|
+
);
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
const renderSkeletonRows = () => {
|
|
107
|
+
return Array.from({ length: skeletonRowCount }, (_, rowIndex) => (
|
|
108
|
+
<tr key={`skeleton-${rowIndex}`} className="dropin-table__body__row">
|
|
109
|
+
{columns.map((column) => (
|
|
110
|
+
<td key={column.key} className="dropin-table__body__cell" data-label={column.label}>
|
|
111
|
+
<Skeleton>
|
|
112
|
+
<SkeletonRow variant="row" size="small" fullWidth />
|
|
113
|
+
</Skeleton>
|
|
114
|
+
</td>
|
|
115
|
+
))}
|
|
116
|
+
</tr>
|
|
117
|
+
));
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
const renderDataRows = () => {
|
|
121
|
+
return rowData.map((row, rowIndex) => {
|
|
122
|
+
const hasDetails = row._rowDetails !== undefined;
|
|
123
|
+
const isExpanded = expandedRows.has(rowIndex);
|
|
124
|
+
|
|
125
|
+
return (
|
|
126
|
+
<Fragment key={rowIndex}>
|
|
127
|
+
<tr className="dropin-table__body__row">
|
|
128
|
+
{columns.map((column) => {
|
|
129
|
+
const cell = row[column.key];
|
|
130
|
+
|
|
131
|
+
if (typeof cell === 'string' || typeof cell === 'number') {
|
|
132
|
+
return (
|
|
133
|
+
<td key={column.key} className="dropin-table__body__cell" data-label={column.label}>
|
|
134
|
+
{cell}
|
|
135
|
+
</td>
|
|
136
|
+
);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
return (
|
|
140
|
+
<td key={column.key} className="dropin-table__body__cell" data-label={column.label}>
|
|
141
|
+
<VComponent node={cell!} />
|
|
142
|
+
</td>
|
|
143
|
+
);
|
|
144
|
+
})}
|
|
145
|
+
</tr>
|
|
146
|
+
{hasDetails && isExpanded && (
|
|
147
|
+
<tr
|
|
148
|
+
key={`${rowIndex}-details`}
|
|
149
|
+
className="dropin-table__row-details dropin-table__row-details--expanded"
|
|
150
|
+
id={`row-${rowIndex}-details`}
|
|
151
|
+
>
|
|
152
|
+
<td
|
|
153
|
+
className="dropin-table__row-details__cell"
|
|
154
|
+
colSpan={columns.length}
|
|
155
|
+
role="region"
|
|
156
|
+
aria-labelledby={`row-${rowIndex}-details`}
|
|
157
|
+
>
|
|
158
|
+
{typeof row._rowDetails === 'string' ? row._rowDetails : <VComponent node={row._rowDetails!} />}
|
|
159
|
+
</td>
|
|
160
|
+
</tr>
|
|
161
|
+
)}
|
|
162
|
+
</Fragment>
|
|
163
|
+
);
|
|
164
|
+
});
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
const getAriaSort = (column: Column): 'none' | 'ascending' | 'descending' | 'other' | undefined => {
|
|
168
|
+
if (column.sortBy === true) return 'none';
|
|
169
|
+
if (column.sortBy === 'asc') return 'ascending';
|
|
170
|
+
if (column.sortBy === 'desc') return 'descending';
|
|
171
|
+
return undefined;
|
|
172
|
+
};
|
|
173
|
+
|
|
174
|
+
return (
|
|
175
|
+
<div className={classes(['dropin-table', `dropin-table--mobile-layout-${mobileLayout}`, className])}>
|
|
176
|
+
<table {...props} className="dropin-table__table">
|
|
177
|
+
{caption && <caption className="dropin-table__caption">{caption}</caption>}
|
|
178
|
+
<thead className="dropin-table__header">
|
|
179
|
+
<tr className="dropin-table__header__row">
|
|
180
|
+
{columns.map((column) => (
|
|
181
|
+
<th
|
|
182
|
+
key={column.key}
|
|
183
|
+
className={classes([
|
|
184
|
+
'dropin-table__header__cell',
|
|
185
|
+
['dropin-table__header__cell--sorted', column.sortBy === 'asc' || column.sortBy === 'desc'],
|
|
186
|
+
['dropin-table__header__cell--sortable', column.sortBy !== undefined]
|
|
187
|
+
])}
|
|
188
|
+
aria-sort={getAriaSort(column)}
|
|
189
|
+
>
|
|
190
|
+
{column.label}
|
|
191
|
+
{renderSortButton(column)}
|
|
192
|
+
</th>
|
|
193
|
+
))}
|
|
194
|
+
</tr>
|
|
195
|
+
</thead>
|
|
196
|
+
<tbody className="dropin-table__body">
|
|
197
|
+
{loading ? (
|
|
198
|
+
// Render skeleton rows when loading
|
|
199
|
+
renderSkeletonRows()
|
|
200
|
+
) : (
|
|
201
|
+
// Render actual data when not loading
|
|
202
|
+
renderDataRows()
|
|
203
|
+
)}
|
|
204
|
+
</tbody>
|
|
205
|
+
</table>
|
|
206
|
+
</div>
|
|
207
|
+
);
|
|
208
|
+
};
|
|
@@ -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
|
-
|
|
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
|
|
109
|
-
|
|
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
|
|
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}
|
package/src/components/index.ts
CHANGED
|
@@ -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';
|
package/src/docs/slots.mdx
CHANGED
|
@@ -32,6 +32,12 @@ The `<Slot />` component is used to define a slot in a container. It receives a
|
|
|
32
32
|
|
|
33
33
|
The name of the slot in _PascalCase_. `string` (required).
|
|
34
34
|
|
|
35
|
+
### lazy
|
|
36
|
+
|
|
37
|
+
Controls whether the slot should be loaded immediately or deferred for later initialization. `boolean` (optional).
|
|
38
|
+
|
|
39
|
+
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.
|
|
40
|
+
|
|
35
41
|
### slotTag
|
|
36
42
|
|
|
37
43
|
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 +77,7 @@ Example:
|
|
|
71
77
|
|
|
72
78
|
- `ctx`: An object representing the context of the slot, including methods for manipulating the slot's content.
|
|
73
79
|
|
|
74
|
-
The slot property, which is implemented as a promise function, provides developers with the flexibility to dynamically generate and manipulate content within slots.
|
|
80
|
+
The slot property, which is implemented as a promise function, provides developers with the flexibility to dynamically generate and manipulate content within slots.
|
|
75
81
|
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
82
|
|
|
77
83
|
### context
|
package/src/i18n/en_US.json
CHANGED
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 { 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
|
-
|
|
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;
|
|
@@ -149,18 +154,21 @@ export function useSlot<K, V extends HTMLElement>(
|
|
|
149
154
|
// @ts-ignore
|
|
150
155
|
context._registerMethod = _registerMethod;
|
|
151
156
|
|
|
152
|
-
const _htmlElementToVNode = useCallback(
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
refElem
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
157
|
+
const _htmlElementToVNode = useCallback(
|
|
158
|
+
(elem: HTMLElement) => {
|
|
159
|
+
return createElement(
|
|
160
|
+
contentTag,
|
|
161
|
+
{
|
|
162
|
+
'data-slot-html-element': elem.tagName.toLowerCase(),
|
|
163
|
+
ref: (refElem: HTMLElement | null): void => {
|
|
164
|
+
refElem?.appendChild(elem);
|
|
165
|
+
},
|
|
166
|
+
},
|
|
167
|
+
null
|
|
168
|
+
);
|
|
169
|
+
},
|
|
170
|
+
[contentTag]
|
|
171
|
+
);
|
|
164
172
|
|
|
165
173
|
// @ts-ignore
|
|
166
174
|
context._htmlElementToVNode = _htmlElementToVNode;
|
|
@@ -322,7 +330,10 @@ export function useSlot<K, V extends HTMLElement>(
|
|
|
322
330
|
status.current = 'loading';
|
|
323
331
|
|
|
324
332
|
log(`🟩 "${name}" Slot Initialized`);
|
|
325
|
-
await callback(
|
|
333
|
+
await callback(
|
|
334
|
+
context as K & DefaultSlotContext<K>,
|
|
335
|
+
elementRef.current as HTMLDivElement | null
|
|
336
|
+
);
|
|
326
337
|
} catch (error) {
|
|
327
338
|
console.error(`Error in "${callback.name}" Slot callback`, error);
|
|
328
339
|
} finally {
|
|
@@ -336,7 +347,7 @@ export function useSlot<K, V extends HTMLElement>(
|
|
|
336
347
|
// Initialization
|
|
337
348
|
useEffect(() => {
|
|
338
349
|
handleLifeCycleInit().finally(() => {
|
|
339
|
-
if (slotsQueue) {
|
|
350
|
+
if (slotsQueue && slotsQueue.value.has(name)) {
|
|
340
351
|
slotsQueue.value.delete(name);
|
|
341
352
|
slotsQueue.value = new Set(slotsQueue.value);
|
|
342
353
|
}
|
|
@@ -359,6 +370,7 @@ export function useSlot<K, V extends HTMLElement>(
|
|
|
359
370
|
interface SlotPropsComponent<T>
|
|
360
371
|
extends Omit<HTMLAttributes<HTMLElement>, 'slot'> {
|
|
361
372
|
name: string;
|
|
373
|
+
lazy?: boolean;
|
|
362
374
|
slot?: SlotProps<T>;
|
|
363
375
|
context?: Context<T>;
|
|
364
376
|
render?: (props: Record<string, any>) => VNode | VNode[];
|
|
@@ -371,6 +383,7 @@ interface SlotPropsComponent<T>
|
|
|
371
383
|
|
|
372
384
|
export function Slot<T>({
|
|
373
385
|
name,
|
|
386
|
+
lazy = false,
|
|
374
387
|
context,
|
|
375
388
|
slot,
|
|
376
389
|
children,
|
|
@@ -400,11 +413,11 @@ export function Slot<T>({
|
|
|
400
413
|
}
|
|
401
414
|
|
|
402
415
|
// add slot to queue
|
|
403
|
-
if (slotsQueue) {
|
|
416
|
+
if (slotsQueue && lazy === false) {
|
|
404
417
|
slotsQueue.value.add(name);
|
|
405
418
|
slotsQueue.value = new Set(slotsQueue.value);
|
|
406
419
|
}
|
|
407
|
-
}, [name, slotsQueue]);
|
|
420
|
+
}, [name, lazy, slotsQueue]);
|
|
408
421
|
|
|
409
422
|
return createElement(
|
|
410
423
|
slotTag,
|