@adobe-commerce/elsie 2.0.0-alpha-20260717142751 → 2.0.0-beta.1
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/CHANGELOG.md
CHANGED
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
# @adobe-commerce/elsie
|
|
2
2
|
|
|
3
|
-
## 2.0.0-
|
|
3
|
+
## 2.0.0-beta.1
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 256007e: Fix ToggleButton generating invalid HTML ids when value prop contains spaces, breaking aria-labelledby label association
|
|
8
|
+
|
|
9
|
+
## 2.0.0-beta.0
|
|
4
10
|
|
|
5
11
|
### Major Changes
|
|
6
12
|
|
|
@@ -118,7 +124,6 @@
|
|
|
118
124
|
- 58da630: Fix `Picker` accessible name and label association. The `<select>` now falls back to `floatingLabel` or `placeholder` for its `aria-label` when no `name` is provided, and the floating `<label>` is now correctly associated with the rendered `<select>` via its generated id instead of the raw `id` prop.
|
|
119
125
|
- 450c408: fix(Picker): auto-select and emit the sole option when the control auto-disables, so single-option narrowing on configurable PDPs no longer leaves the value unselected and Add to Cart permanently disabled
|
|
120
126
|
- 33ebe8a: Add accessible labels to password validation and input status icons (WCAG 1.1.1)
|
|
121
|
-
- a920177: fix(Incrementer): prevent quantity input flicker during in-progress typing, and prevent double onValue call when debounce fires before blur
|
|
122
127
|
- 2ad7316: Fix `ToggleButton`'s underlying radio input announcing the shared radio-group `name` (e.g. "payment-method") as its accessible name for every option instead of the option's own visible label, which violates WCAG 2.4.6 (Headings and Labels) and 2.5.3 (Label in Name). The radio input's accessible name now defaults to `aria-labelledby` pointing at the option's own visible label content (e.g. "Check / Money order"), which works correctly whether `label` is a string or a `VNode`. An optional `ariaLabel` prop is still available for consumers who need to set an explicit accessible name via `aria-label` instead.
|
|
123
128
|
- 016a558: Fix low-contrast field label text in `Input` when a field is in an error state. The floating label color now meets WCAG AA contrast requirements for normal-size text against light backgrounds, matching the color already used for error text elsewhere (helper text, alerts).
|
|
124
129
|
- 51fcb35: fix(a11y): darken low-contrast focus indicators to meet WCAG 1.4.11 (3:1 non-text contrast)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@adobe-commerce/elsie",
|
|
3
|
-
"version": "2.0.0-
|
|
3
|
+
"version": "2.0.0-beta.1",
|
|
4
4
|
"license": "SEE LICENSE IN LICENSE.md",
|
|
5
5
|
"description": "Domain Package SDK",
|
|
6
6
|
"engines": {
|
|
@@ -30,12 +30,12 @@
|
|
|
30
30
|
"postpublish": "node ./scripts/publish-tools.mjs"
|
|
31
31
|
},
|
|
32
32
|
"devDependencies": {
|
|
33
|
-
"@adobe-commerce/event-bus": "1.1.1-
|
|
34
|
-
"@adobe-commerce/fetch-graphql": "1.3.1-
|
|
35
|
-
"@adobe-commerce/recaptcha": "1.2.1-
|
|
36
|
-
"@adobe-commerce/storefront-design": "1.1.1-
|
|
33
|
+
"@adobe-commerce/event-bus": "~1.1.1-beta.0",
|
|
34
|
+
"@adobe-commerce/fetch-graphql": "~1.3.1-beta.0",
|
|
35
|
+
"@adobe-commerce/recaptcha": "1.2.1-beta.0",
|
|
36
|
+
"@adobe-commerce/storefront-design": "~1.1.1-beta.0",
|
|
37
37
|
"@chromatic-com/storybook": "^5",
|
|
38
|
-
"@dropins/build-tools": "1.2.1-
|
|
38
|
+
"@dropins/build-tools": "~1.2.1-beta.0"
|
|
39
39
|
},
|
|
40
40
|
"peerDependencies": {
|
|
41
41
|
"eslint-plugin-cypress": ">=3.0.0"
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
*******************************************************************/
|
|
9
9
|
|
|
10
10
|
import { FunctionComponent } from 'preact';
|
|
11
|
-
import { useState,
|
|
11
|
+
import { useState, useCallback, useEffect } from 'preact/hooks';
|
|
12
12
|
import { HTMLAttributes } from 'preact/compat';
|
|
13
13
|
import { classes, debounce } from '@adobe-commerce/elsie/lib';
|
|
14
14
|
import { Add, Minus } from '@adobe-commerce/elsie/icons';
|
|
@@ -48,7 +48,6 @@ export const Incrementer: FunctionComponent<IncrementerProps> = ({
|
|
|
48
48
|
}) => {
|
|
49
49
|
const [currentValue, setCurrentValue] = useState<number>(Number(value));
|
|
50
50
|
const [isEmpty, setIsEmpty] = useState<boolean>(false);
|
|
51
|
-
const isFocusedRef = useRef<boolean>(false);
|
|
52
51
|
const minValue = Number(min);
|
|
53
52
|
const maxValue = Number(max);
|
|
54
53
|
const isInvalid = error || isEmpty || currentValue < minValue || currentValue > maxValue;
|
|
@@ -61,45 +60,38 @@ export const Incrementer: FunctionComponent<IncrementerProps> = ({
|
|
|
61
60
|
? 'Dropin.Incrementer.maxQuantityMessage'
|
|
62
61
|
: 'Dropin.Incrementer.errorMessage';
|
|
63
62
|
|
|
64
|
-
//
|
|
65
|
-
const onValueRef = useRef(onValue);
|
|
66
|
-
useEffect(() => { onValueRef.current = onValue; }, [onValue]);
|
|
67
|
-
|
|
68
|
-
const onUpdateErrorRef = useRef(onUpdateError);
|
|
69
|
-
useEffect(() => { onUpdateErrorRef.current = onUpdateError; }, [onUpdateError]);
|
|
70
|
-
|
|
71
|
-
const invoke = (newValue: number) => {
|
|
72
|
-
try {
|
|
73
|
-
onValueRef.current?.(newValue);
|
|
74
|
-
} catch (e) {
|
|
75
|
-
onUpdateErrorRef.current?.(e as Error);
|
|
76
|
-
}
|
|
77
|
-
};
|
|
78
|
-
|
|
79
|
-
// Created once — stable across re-renders caused by cart/data events
|
|
80
|
-
// Buttons: 200ms debounce, created once, never recreated
|
|
81
|
-
const debouncedButtonHandler = useRef(debounce(invoke, 200)).current;
|
|
82
|
-
|
|
83
|
-
// True while the input debounce timer is still pending.
|
|
84
|
-
// Blur only calls invoke if it's cancelling a live timer — not if the timer already fired.
|
|
85
|
-
const hasPendingInputRef = useRef(false);
|
|
86
|
-
const debouncedInputHandler = useRef(
|
|
87
|
-
debounce((newValue: number) => {
|
|
88
|
-
hasPendingInputRef.current = false;
|
|
89
|
-
invoke(newValue);
|
|
90
|
-
}, 1000)
|
|
91
|
-
).current;
|
|
92
|
-
|
|
93
|
-
// Skip server sync while user is actively typing to prevent flicker
|
|
63
|
+
// Add this effect to synchronize internal state with external value prop
|
|
94
64
|
useEffect(() => {
|
|
95
65
|
const propValue = Number(value);
|
|
96
|
-
if (
|
|
66
|
+
if (propValue !== currentValue) {
|
|
97
67
|
setCurrentValue(propValue);
|
|
98
68
|
setIsEmpty(false);
|
|
99
69
|
}
|
|
100
70
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
101
71
|
}, [value]);
|
|
102
72
|
|
|
73
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
74
|
+
const debouncedOnValueHandler = useCallback(
|
|
75
|
+
debounce(async (newValue: any) => {
|
|
76
|
+
if (onValue) {
|
|
77
|
+
try {
|
|
78
|
+
onValue(newValue);
|
|
79
|
+
} catch (e) {
|
|
80
|
+
if (onUpdateError) {
|
|
81
|
+
onUpdateError(e as Error);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}, 200),
|
|
86
|
+
[onValue, onUpdateError]
|
|
87
|
+
);
|
|
88
|
+
|
|
89
|
+
const handleIncrementer = (_value: number) => {
|
|
90
|
+
setIsEmpty(false);
|
|
91
|
+
debouncedOnValueHandler(_value);
|
|
92
|
+
setCurrentValue(_value);
|
|
93
|
+
};
|
|
94
|
+
|
|
103
95
|
return (
|
|
104
96
|
<div
|
|
105
97
|
className={classes([
|
|
@@ -133,8 +125,7 @@ export const Incrementer: FunctionComponent<IncrementerProps> = ({
|
|
|
133
125
|
'dropin-incrementer__decrease-button',
|
|
134
126
|
[`dropin-incrementer__decrease-button--disabled`, disabled],
|
|
135
127
|
])}
|
|
136
|
-
|
|
137
|
-
onClick={() => { setIsEmpty(false); debouncedInputHandler.cancel(); hasPendingInputRef.current = false; setCurrentValue(currentValue - 1); debouncedButtonHandler(currentValue - 1); }}
|
|
128
|
+
onClick={() => handleIncrementer(currentValue - 1)}
|
|
138
129
|
disabled={disabled || currentValue < minValue + 1}
|
|
139
130
|
aria-label={
|
|
140
131
|
(<Text id="Dropin.Incrementer.decreaseLabel" />) as any
|
|
@@ -162,24 +153,17 @@ export const Incrementer: FunctionComponent<IncrementerProps> = ({
|
|
|
162
153
|
name={name}
|
|
163
154
|
value={isEmpty ? '' : currentValue}
|
|
164
155
|
disabled={disabled}
|
|
165
|
-
onFocus={() => { isFocusedRef.current = true; }}
|
|
166
156
|
onBlur={() => {
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
hasPendingInputRef.current = false;
|
|
171
|
-
if (!isEmpty && wasPending) invoke(Number(currentValue));
|
|
157
|
+
if (!isEmpty) {
|
|
158
|
+
handleIncrementer(Number(currentValue));
|
|
159
|
+
}
|
|
172
160
|
}}
|
|
173
161
|
onChange={(e) => {
|
|
174
162
|
const newValue = e.currentTarget.value;
|
|
175
163
|
if (newValue === '') {
|
|
176
164
|
setIsEmpty(true);
|
|
177
165
|
} else {
|
|
178
|
-
|
|
179
|
-
setCurrentValue(Number(newValue));
|
|
180
|
-
debouncedButtonHandler.cancel();
|
|
181
|
-
hasPendingInputRef.current = true;
|
|
182
|
-
debouncedInputHandler(Number(newValue));
|
|
166
|
+
handleIncrementer(Number(newValue));
|
|
183
167
|
}
|
|
184
168
|
}}
|
|
185
169
|
{...props}
|
|
@@ -200,8 +184,7 @@ export const Incrementer: FunctionComponent<IncrementerProps> = ({
|
|
|
200
184
|
'dropin-incrementer__increase-button',
|
|
201
185
|
[`dropin-incrementer__increase-button--disabled`, disabled],
|
|
202
186
|
])}
|
|
203
|
-
|
|
204
|
-
onClick={() => { setIsEmpty(false); debouncedInputHandler.cancel(); hasPendingInputRef.current = false; setCurrentValue(currentValue + 1); debouncedButtonHandler(currentValue + 1); }}
|
|
187
|
+
onClick={() => handleIncrementer(currentValue + 1)}
|
|
205
188
|
disabled={disabled || currentValue > maxValue - 1}
|
|
206
189
|
aria-label={
|
|
207
190
|
(<Text id="Dropin.Incrementer.increaseLabel" />) as any
|
|
@@ -42,7 +42,7 @@ export const ToggleButton: FunctionComponent<ToggleButtonProps> = ({
|
|
|
42
42
|
selected = true,
|
|
43
43
|
...props
|
|
44
44
|
}) => {
|
|
45
|
-
const contentId = `dropin-toggle-button__content-${name}-${value}
|
|
45
|
+
const contentId = `dropin-toggle-button__content-${name}-${value}`.replace(/\s+/g, '-');
|
|
46
46
|
|
|
47
47
|
return (
|
|
48
48
|
<div
|