@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.
- 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 +761 -0
- package/src/components/Table/Table.tsx +249 -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 +9 -1
- package/src/i18n/en_US.json +5 -0
- package/src/lib/aem/configs.ts +7 -4
- package/src/lib/slot.tsx +53 -26
|
@@ -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
|
-
|
|
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
|
@@ -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) => {
|
package/src/i18n/en_US.json
CHANGED
package/src/lib/aem/configs.ts
CHANGED
|
@@ -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 {
|
|
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;
|
|
@@ -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(
|
|
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
|
}
|
|
@@ -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,
|