@adobe-commerce/elsie 1.5.1-alpha003 → 1.5.1-alpha2861
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
|
@@ -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';
|
|
@@ -14,6 +14,7 @@ import {
|
|
|
14
14
|
useEffect,
|
|
15
15
|
useRef,
|
|
16
16
|
useCallback,
|
|
17
|
+
useMemo,
|
|
17
18
|
} from 'preact/compat';
|
|
18
19
|
import { classes } from '@adobe-commerce/elsie/lib';
|
|
19
20
|
import '@adobe-commerce/elsie/components/TextSwatch/TextSwatch.css';
|
|
@@ -93,6 +94,8 @@ export const TextSwatch: FunctionComponent<TextSwatchProps> = ({
|
|
|
93
94
|
}
|
|
94
95
|
}, [label]);
|
|
95
96
|
|
|
97
|
+
const uniqueId = useMemo(() => id ?? `${name}_${id}_${Math.random().toString(36)}`, [name, id]);
|
|
98
|
+
|
|
96
99
|
return (
|
|
97
100
|
<div
|
|
98
101
|
className="dropin-text-swatch__container"
|
|
@@ -101,7 +104,7 @@ export const TextSwatch: FunctionComponent<TextSwatchProps> = ({
|
|
|
101
104
|
<input
|
|
102
105
|
type={multi ? 'checkbox' : 'radio'}
|
|
103
106
|
name={name}
|
|
104
|
-
id={
|
|
107
|
+
id={uniqueId}
|
|
105
108
|
value={value}
|
|
106
109
|
aria-label={handleAriaLabel()}
|
|
107
110
|
checked={selected}
|
|
@@ -116,7 +119,7 @@ export const TextSwatch: FunctionComponent<TextSwatchProps> = ({
|
|
|
116
119
|
])}
|
|
117
120
|
/>
|
|
118
121
|
<label
|
|
119
|
-
htmlFor={
|
|
122
|
+
htmlFor={uniqueId}
|
|
120
123
|
ref={spanRef}
|
|
121
124
|
className={classes([
|
|
122
125
|
'dropin-text-swatch__label',
|
|
@@ -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
|
@@ -2,9 +2,9 @@
|
|
|
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
10
|
export * from '@adobe-commerce/elsie/components/Skeleton';
|
|
@@ -49,3 +49,5 @@ 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';
|
|
53
|
+
export * from '@adobe-commerce/elsie/components/MultiSelect';
|
package/src/docs/API/render.mdx
CHANGED
|
@@ -121,5 +121,21 @@ button.addEventListener('click', () => {
|
|
|
121
121
|
});
|
|
122
122
|
```
|
|
123
123
|
|
|
124
|
+
### Unmounting components without instance access
|
|
125
|
+
|
|
126
|
+
The `Render.unmount` static method provides a way to unmount components from the DOM when you don't have direct access to the component instance.
|
|
127
|
+
This is particularly useful in scenarios where components are rendered inside modals, dialogs, or other temporary containers that need to be cleaned up.
|
|
128
|
+
|
|
129
|
+
#### Example
|
|
130
|
+
|
|
131
|
+
```js
|
|
132
|
+
// Close the dialog
|
|
133
|
+
dialog.close();
|
|
134
|
+
|
|
135
|
+
// Unmount any dropin containers rendered in the modal
|
|
136
|
+
dialog.querySelectorAll('[data-dropin-container]').forEach(Render.unmount);
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
This approach ensures that all dropin components are properly cleaned up when their container elements are removed from the DOM, preventing memory leaks and maintaining application performance.
|
|
124
140
|
</Unstyled>
|
|
125
141
|
|
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
|
@@ -141,6 +141,44 @@
|
|
|
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}"
|
|
149
|
+
},
|
|
150
|
+
"MultiSelect": {
|
|
151
|
+
"selectAll": "Select All",
|
|
152
|
+
"deselectAll": "Deselect All",
|
|
153
|
+
"placeholder": "Select options",
|
|
154
|
+
"noResultsText": "No options available",
|
|
155
|
+
"ariaLabel":{
|
|
156
|
+
"removed": "removed",
|
|
157
|
+
"added": "added",
|
|
158
|
+
"itemsSelected": "items selected",
|
|
159
|
+
"itemsAdded": "items added",
|
|
160
|
+
"itemsRemoved": "items removed",
|
|
161
|
+
"selectedTotal": "selected total",
|
|
162
|
+
"noResultsFor": "No results found for",
|
|
163
|
+
"optionsAvailable": "options available",
|
|
164
|
+
"dropdownExpanded": "Dropdown expanded",
|
|
165
|
+
"useArrowKeys": "Use arrow keys to navigate",
|
|
166
|
+
"removeFromSelection": "Remove",
|
|
167
|
+
"fromSelection": "from selection",
|
|
168
|
+
"selectedItem": "Selected item:",
|
|
169
|
+
"inField": " in {floatingLabel}",
|
|
170
|
+
"selectedItems": "Selected items",
|
|
171
|
+
"scrollableOptionsList": "Scrollable options list",
|
|
172
|
+
"selectOptions": "Select options",
|
|
173
|
+
"itemAction": "{label} {action}. {count} items selected.",
|
|
174
|
+
"bulkAdded": "{count} items added. {total} items selected total.",
|
|
175
|
+
"bulkRemoved": "{count} items removed. {total} items selected total.",
|
|
176
|
+
"dropdownExpandedWithOptions": "Dropdown expanded. {count} option{s} available. Use arrow keys to navigate.",
|
|
177
|
+
"selectedItemInField": "Selected item: {label} in {field}",
|
|
178
|
+
"removeFromSelectionWithText": "Remove {label} from selection. {text}",
|
|
179
|
+
"itemsSelectedDescription": "{count} item{s} selected: {labels}",
|
|
180
|
+
"noItemsSelected": "No items selected"
|
|
181
|
+
}
|
|
144
182
|
}
|
|
145
183
|
}
|
|
146
184
|
}
|
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/render.tsx
CHANGED
|
@@ -71,17 +71,21 @@ export class Render {
|
|
|
71
71
|
rootElement.innerHTML = '';
|
|
72
72
|
|
|
73
73
|
// clone the root element to initialize rendering on the background
|
|
74
|
-
const
|
|
74
|
+
const root = document.createElement('div');
|
|
75
75
|
|
|
76
76
|
// apply base design tokens and global styles to the root element
|
|
77
77
|
rootElement.classList.add('dropin-design');
|
|
78
|
+
rootElement.setAttribute('data-dropin-container', Component.name);
|
|
78
79
|
|
|
79
|
-
|
|
80
|
+
// store the virtual root element
|
|
81
|
+
(rootElement as any).__rootElement = root;
|
|
82
|
+
|
|
83
|
+
render(<Root next={state} />, root);
|
|
80
84
|
|
|
81
85
|
// API object to control the rendered component
|
|
82
86
|
const API: RenderAPI = {
|
|
83
87
|
remove: () => {
|
|
84
|
-
render(null,
|
|
88
|
+
render(null, root);
|
|
85
89
|
},
|
|
86
90
|
setProps: (cb: (prev: T) => T) => {
|
|
87
91
|
const next = cb(state.peek());
|
|
@@ -97,7 +101,7 @@ export class Render {
|
|
|
97
101
|
rootElement.classList.add('dropin-design');
|
|
98
102
|
|
|
99
103
|
// append the rendered component to the DOM only when all slots are resolved
|
|
100
|
-
rootElement.appendChild(
|
|
104
|
+
rootElement.appendChild(root.firstChild ?? root);
|
|
101
105
|
|
|
102
106
|
return resolve(API);
|
|
103
107
|
}
|
|
@@ -106,14 +110,24 @@ export class Render {
|
|
|
106
110
|
};
|
|
107
111
|
}
|
|
108
112
|
|
|
113
|
+
/**
|
|
114
|
+
* Unmounts a container from a root element.
|
|
115
|
+
* @param rootElement - The root element to unmount the container from.
|
|
116
|
+
*/
|
|
117
|
+
static unmount(rootElement: HTMLElement) {
|
|
118
|
+
const root = (rootElement as any)?.__rootElement;
|
|
119
|
+
if (!root) throw new Error('Root element is not defined');
|
|
120
|
+
render(null, root);
|
|
121
|
+
}
|
|
122
|
+
|
|
109
123
|
/**
|
|
110
124
|
* UnRenders a component from a root element.
|
|
111
125
|
* @param rootElement - The root element to unmount the component from.
|
|
112
|
-
* @deprecated Use `remove` method from the returned object of the `mount` method instead.
|
|
126
|
+
* @deprecated Use `remove` method from the returned object of the `mount` method instead or `unmount` method from the `Render` class.
|
|
113
127
|
*/
|
|
114
128
|
unmount(rootElement: HTMLElement) {
|
|
115
129
|
if (!rootElement) throw new Error('Root element is not defined');
|
|
116
|
-
rootElement.firstChild?.remove();
|
|
130
|
+
rootElement.firstChild?.remove();
|
|
117
131
|
}
|
|
118
132
|
|
|
119
133
|
/**
|
|
@@ -135,4 +149,4 @@ export class Render {
|
|
|
135
149
|
{ ...options }
|
|
136
150
|
);
|
|
137
151
|
}
|
|
138
|
-
}
|
|
152
|
+
}
|