@sapudex/web-components 2.44.0 → 2.46.0
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/cypress/specs/DatePicker.cy.tsx +259 -0
- package/cypress/specs/MastheadItem.cy.tsx +88 -0
- package/cypress/specs/MastheadShell.cy.tsx +3 -1
- package/cypress/specs/TagGroup.cy.tsx +104 -0
- package/cypress/specs/TextField.cy.tsx +64 -1
- package/cypress/support/component-imports.instrumented.ts +2 -0
- package/cypress/support/component-imports.src.ts +2 -0
- package/dist/Carousel.d.ts +5 -1
- package/dist/Carousel.js +10 -1
- package/dist/Carousel.js.map +1 -1
- package/dist/DatePicker.d.ts +4 -0
- package/dist/DatePicker.js +19 -13
- package/dist/DatePicker.js.map +1 -1
- package/dist/DatePickerPopoverTemplate.js +1 -1
- package/dist/DatePickerPopoverTemplate.js.map +1 -1
- package/dist/DatePickerTemplate.js +1 -1
- package/dist/DatePickerTemplate.js.map +1 -1
- package/dist/MastheadItem.d.ts +6 -3
- package/dist/MastheadItem.js +18 -0
- package/dist/MastheadItem.js.map +1 -1
- package/dist/MastheadMobileMenu.d.ts +1 -0
- package/dist/MastheadMobileMenu.js +5 -0
- package/dist/MastheadMobileMenu.js.map +1 -1
- package/dist/MastheadProfileMenu.d.ts +5 -4
- package/dist/MastheadProfileMenu.js +15 -12
- package/dist/MastheadProfileMenu.js.map +1 -1
- package/dist/MastheadShell.d.ts +2 -0
- package/dist/MastheadShell.js +9 -2
- package/dist/MastheadShell.js.map +1 -1
- package/dist/SelectBoxTemplate.js +1 -1
- package/dist/SelectBoxTemplate.js.map +1 -1
- package/dist/TagGroup.d.ts +5 -1
- package/dist/TagGroup.js +51 -7
- package/dist/TagGroup.js.map +1 -1
- package/dist/TagGroupTemplate.js +1 -1
- package/dist/TagGroupTemplate.js.map +1 -1
- package/dist/TextField.d.ts +7 -0
- package/dist/TextField.js +10 -0
- package/dist/TextField.js.map +1 -1
- package/dist/TextFieldTemplate.js +1 -1
- package/dist/TextFieldTemplate.js.map +1 -1
- package/dist/Tooltip.js +2 -1
- package/dist/Tooltip.js.map +1 -1
- package/dist/assets/{InputSuggestions-B3D8lxAS.js → InputSuggestions-BwpJ8Exf.js} +1 -1
- package/dist/assets/{ListItemStandardExpandableTextTemplate-Is8_hDTt.js → ListItemStandardExpandableTextTemplate-7iRPZq6L.js} +1 -1
- package/dist/assets/{bundle.esm-6rH7NAG7.js → bundle.esm-BbkM4Bz9.js} +247 -245
- package/dist/assets/messagebundle_pt-D9rap3lj.js +1 -0
- package/dist/assets/messagebundle_ro-BTNJ9YdP.js +1 -0
- package/dist/css/themes/CountrySelector.css +1 -1
- package/dist/css/themes/MastheadCountrySelector.css +1 -1
- package/dist/css/themes/MastheadItem.css +1 -1
- package/dist/css/themes/MastheadMenu.css +1 -1
- package/dist/css/themes/MastheadMobileMenu.css +1 -1
- package/dist/css/themes/MastheadProfileMenu.css +1 -1
- package/dist/css/themes/TagGroup.css +1 -1
- package/dist/custom-elements-internal.json +68 -17
- package/dist/custom-elements.json +75 -17
- package/dist/generated/assets/i18n/messagebundle_pt.json +1 -1
- package/dist/generated/assets/i18n/messagebundle_ro.json +1 -1
- package/dist/generated/templates/MastheadProfileMenuTemplate.lit.js +2 -2
- package/dist/generated/templates/MastheadProfileMenuTemplate.lit.js.map +1 -1
- package/dist/generated/templates/MastheadShellTemplate.lit.js +2 -2
- package/dist/generated/templates/MastheadShellTemplate.lit.js.map +1 -1
- package/dist/generated/themes/CountrySelector.css.d.ts +1 -1
- package/dist/generated/themes/CountrySelector.css.js +1 -1
- package/dist/generated/themes/CountrySelector.css.js.map +1 -1
- package/dist/generated/themes/MastheadCountrySelector.css.d.ts +1 -1
- package/dist/generated/themes/MastheadCountrySelector.css.js +1 -1
- package/dist/generated/themes/MastheadCountrySelector.css.js.map +1 -1
- package/dist/generated/themes/MastheadItem.css.d.ts +1 -1
- package/dist/generated/themes/MastheadItem.css.js +1 -1
- package/dist/generated/themes/MastheadItem.css.js.map +1 -1
- package/dist/generated/themes/MastheadMenu.css.d.ts +1 -1
- package/dist/generated/themes/MastheadMenu.css.js +1 -1
- package/dist/generated/themes/MastheadMenu.css.js.map +1 -1
- package/dist/generated/themes/MastheadMobileMenu.css.d.ts +1 -1
- package/dist/generated/themes/MastheadMobileMenu.css.js +1 -1
- package/dist/generated/themes/MastheadMobileMenu.css.js.map +1 -1
- package/dist/generated/themes/MastheadProfileMenu.css.d.ts +1 -1
- package/dist/generated/themes/MastheadProfileMenu.css.js +1 -1
- package/dist/generated/themes/MastheadProfileMenu.css.js.map +1 -1
- package/dist/generated/themes/TagGroup.css.d.ts +1 -1
- package/dist/generated/themes/TagGroup.css.js +1 -1
- package/dist/generated/themes/TagGroup.css.js.map +1 -1
- package/dist/messagebundle_pt.properties +126 -0
- package/dist/messagebundle_ro.properties +126 -0
- package/dist/vscode.html-custom-data.json +11 -6
- package/dist/web-types.json +37 -14
- package/package.json +8 -8
- package/src/Carousel.ts +14 -1
- package/src/DatePicker.ts +29 -11
- package/src/DatePickerPopoverTemplate.tsx +2 -1
- package/src/DatePickerTemplate.tsx +1 -0
- package/src/MastheadItem.ts +24 -3
- package/src/MastheadMobileMenu.ts +6 -0
- package/src/MastheadProfileMenu.hbs +25 -25
- package/src/MastheadProfileMenu.ts +20 -16
- package/src/MastheadShell.hbs +3 -2
- package/src/MastheadShell.ts +11 -2
- package/src/SelectBoxTemplate.tsx +1 -1
- package/src/TagGroup.ts +59 -6
- package/src/TagGroupTemplate.tsx +1 -0
- package/src/TextField.ts +9 -0
- package/src/TextFieldTemplate.tsx +1 -0
- package/src/Tooltip.ts +2 -1
- package/src/generated/templates/MastheadProfileMenuTemplate.lit.ts +2 -2
- package/src/generated/templates/MastheadShellTemplate.lit.ts +2 -2
- package/src/generated/themes/CountrySelector.css.ts +1 -1
- package/src/generated/themes/MastheadCountrySelector.css.ts +1 -1
- package/src/generated/themes/MastheadItem.css.ts +1 -1
- package/src/generated/themes/MastheadMenu.css.ts +1 -1
- package/src/generated/themes/MastheadMobileMenu.css.ts +1 -1
- package/src/generated/themes/MastheadProfileMenu.css.ts +1 -1
- package/src/generated/themes/TagGroup.css.ts +1 -1
- package/src/i18n/messagebundle_pt.properties +126 -0
- package/src/i18n/messagebundle_ro.properties +126 -0
- package/src/themes/CountrySelector.css +5 -1
- package/src/themes/MastheadCountrySelector.css +29 -33
- package/src/themes/MastheadItem.css +53 -13
- package/src/themes/MastheadMenu.css +1 -11
- package/src/themes/MastheadMobileMenu.css +0 -12
- package/src/themes/MastheadProfileMenu.css +22 -16
- package/src/themes/TagGroup.css +1 -2
- package/dist/assets/messagebundle_pt-DPeE7NbC.js +0 -1
- package/dist/assets/messagebundle_ro-CN4kAAYE.js +0 -1
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
import { UDExDatePicker } from "../support/component-imports.js";
|
|
2
|
+
|
|
3
|
+
describe("Date Picker", () => {
|
|
4
|
+
describe("formatPattern as native placeholder", () => {
|
|
5
|
+
// Updated: TextField now only renders the placeholder on the native input while active (focused),
|
|
6
|
+
// to avoid overlap with the floating label. Each test asserts absent before focus, present after focus.
|
|
7
|
+
// The placeholder is derived from `_effectivePlaceholder` — which returns
|
|
8
|
+
// `getFormat().oFormatOptions.pattern || formatPattern`. With the default
|
|
9
|
+
// `formatPattern = "MMM dd, yyyy"`, UI5's DateFormat honors that pattern, so the
|
|
10
|
+
// resolved value matches the property value.
|
|
11
|
+
it("passes the default formatPattern as the inner text field's native input placeholder when active", () => {
|
|
12
|
+
cy.mount(<UDExDatePicker id="defaultDatePicker" label="Date"></UDExDatePicker>);
|
|
13
|
+
|
|
14
|
+
// Before focus: placeholder is not rendered on the native input.
|
|
15
|
+
cy.get("#defaultDatePicker")
|
|
16
|
+
.shadow()
|
|
17
|
+
.find("[udex-text-field]")
|
|
18
|
+
.shadow()
|
|
19
|
+
.find("input")
|
|
20
|
+
.then($input => {
|
|
21
|
+
const placeholder = $input.attr("placeholder");
|
|
22
|
+
expect(placeholder === undefined || placeholder === "").to.equal(true);
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
// Focus the native input to switch the field to active state.
|
|
26
|
+
cy.get("#defaultDatePicker")
|
|
27
|
+
.shadow()
|
|
28
|
+
.find("[udex-text-field]")
|
|
29
|
+
.shadow()
|
|
30
|
+
.find("input")
|
|
31
|
+
.focus();
|
|
32
|
+
|
|
33
|
+
// After focus: the default formatPattern is exposed as the native placeholder.
|
|
34
|
+
cy.get("#defaultDatePicker")
|
|
35
|
+
.shadow()
|
|
36
|
+
.find("[udex-text-field]")
|
|
37
|
+
.shadow()
|
|
38
|
+
.find("input")
|
|
39
|
+
.should("have.attr", "placeholder", "MMM dd, yyyy");
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
// Updated: same active-gate applies for explicit formatPattern.
|
|
43
|
+
it("passes an explicit formatPattern to the inner text field's native input placeholder when active", () => {
|
|
44
|
+
cy.mount(
|
|
45
|
+
<UDExDatePicker
|
|
46
|
+
id="customPatternDatePicker"
|
|
47
|
+
label="Date"
|
|
48
|
+
formatPattern="MM/dd/yyyy"
|
|
49
|
+
></UDExDatePicker>,
|
|
50
|
+
);
|
|
51
|
+
|
|
52
|
+
// Before focus: placeholder is absent.
|
|
53
|
+
cy.get("#customPatternDatePicker")
|
|
54
|
+
.shadow()
|
|
55
|
+
.find("[udex-text-field]")
|
|
56
|
+
.shadow()
|
|
57
|
+
.find("input")
|
|
58
|
+
.then($input => {
|
|
59
|
+
const placeholder = $input.attr("placeholder");
|
|
60
|
+
expect(placeholder === undefined || placeholder === "").to.equal(true);
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
// Focus, then assert the explicit pattern is rendered.
|
|
64
|
+
cy.get("#customPatternDatePicker")
|
|
65
|
+
.shadow()
|
|
66
|
+
.find("[udex-text-field]")
|
|
67
|
+
.shadow()
|
|
68
|
+
.find("input")
|
|
69
|
+
.focus();
|
|
70
|
+
|
|
71
|
+
cy.get("#customPatternDatePicker")
|
|
72
|
+
.shadow()
|
|
73
|
+
.find("[udex-text-field]")
|
|
74
|
+
.shadow()
|
|
75
|
+
.find("input")
|
|
76
|
+
.should("have.attr", "placeholder", "MM/dd/yyyy");
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
// Updated: reactive update is only observable while the field is active.
|
|
80
|
+
it("updates the native input placeholder when formatPattern changes (while active)", () => {
|
|
81
|
+
cy.mount(
|
|
82
|
+
<UDExDatePicker
|
|
83
|
+
id="reactivePatternDatePicker"
|
|
84
|
+
formatPattern="MMM dd, yyyy"
|
|
85
|
+
></UDExDatePicker>,
|
|
86
|
+
);
|
|
87
|
+
|
|
88
|
+
// Focus so the placeholder is actually rendered on the native input.
|
|
89
|
+
cy.get("#reactivePatternDatePicker")
|
|
90
|
+
.shadow()
|
|
91
|
+
.find("[udex-text-field]")
|
|
92
|
+
.shadow()
|
|
93
|
+
.find("input")
|
|
94
|
+
.focus();
|
|
95
|
+
|
|
96
|
+
cy.get("#reactivePatternDatePicker")
|
|
97
|
+
.shadow()
|
|
98
|
+
.find("[udex-text-field]")
|
|
99
|
+
.shadow()
|
|
100
|
+
.find("input")
|
|
101
|
+
.should("have.attr", "placeholder", "MMM dd, yyyy");
|
|
102
|
+
|
|
103
|
+
cy.get("#reactivePatternDatePicker").then($el => {
|
|
104
|
+
($el[0] as unknown as { formatPattern: string }).formatPattern = "MM/dd/yyyy";
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
// Keep focus so the field remains active, then assert the updated pattern is reflected.
|
|
108
|
+
cy.get("#reactivePatternDatePicker")
|
|
109
|
+
.shadow()
|
|
110
|
+
.find("[udex-text-field]")
|
|
111
|
+
.shadow()
|
|
112
|
+
.find("input")
|
|
113
|
+
.focus();
|
|
114
|
+
|
|
115
|
+
cy.get("#reactivePatternDatePicker")
|
|
116
|
+
.shadow()
|
|
117
|
+
.find("[udex-text-field]")
|
|
118
|
+
.shadow()
|
|
119
|
+
.find("input")
|
|
120
|
+
.should("have.attr", "placeholder", "MM/dd/yyyy");
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
// Added: DatePicker forwards its resolved placeholder to the inner UDExTextField, which
|
|
124
|
+
// now renders the native placeholder without focus when no label is set. Assert visible
|
|
125
|
+
// before focus so the no-label branch of `active || !this.label` is exercised.
|
|
126
|
+
it("renders the default formatPattern as the native input placeholder without focus when no label is set", () => {
|
|
127
|
+
cy.mount(<UDExDatePicker id="noLabelDatePicker"></UDExDatePicker>);
|
|
128
|
+
|
|
129
|
+
// DatePicker has no `label` set, so the inner TextField skips the active gate.
|
|
130
|
+
cy.get("#noLabelDatePicker")
|
|
131
|
+
.shadow()
|
|
132
|
+
.find("[udex-text-field]")
|
|
133
|
+
.shadow()
|
|
134
|
+
.find("input")
|
|
135
|
+
.should("have.attr", "placeholder", "MMM dd, yyyy");
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
// Updated: `_effectivePlaceholder` now falls back to `UDExDatePicker.DEFAULT_FORMAT_PATTERN`
|
|
139
|
+
// ("MMM dd, yyyy") whenever `formatPattern` is falsy — covers both the "never set" case and
|
|
140
|
+
// the Storybook case where clearing the control sets `format-pattern=""` on the attribute,
|
|
141
|
+
// which would otherwise override the class-level default of the property.
|
|
142
|
+
it("uses the default formatPattern as placeholder when formatPattern is not explicitly set", () => {
|
|
143
|
+
cy.mount(<UDExDatePicker id="defaultPatternDatePicker" label="Date"></UDExDatePicker>);
|
|
144
|
+
|
|
145
|
+
cy.get("#defaultPatternDatePicker")
|
|
146
|
+
.shadow()
|
|
147
|
+
.find("[udex-text-field]")
|
|
148
|
+
.shadow()
|
|
149
|
+
.find("input")
|
|
150
|
+
.focus();
|
|
151
|
+
|
|
152
|
+
cy.get("#defaultPatternDatePicker")
|
|
153
|
+
.shadow()
|
|
154
|
+
.find("[udex-text-field]")
|
|
155
|
+
.shadow()
|
|
156
|
+
.find("input")
|
|
157
|
+
.should("have.attr", "placeholder", "MMM dd, yyyy");
|
|
158
|
+
|
|
159
|
+
// Case 2: explicit empty-string attribute (Storybook's cleared-control behavior).
|
|
160
|
+
// Mount with `format-pattern=""` set via the attribute so it overrides the class default,
|
|
161
|
+
// and confirm the getter still surfaces "MMM dd, yyyy" as the native placeholder.
|
|
162
|
+
cy.mount(
|
|
163
|
+
<UDExDatePicker
|
|
164
|
+
id="emptyPatternDatePicker"
|
|
165
|
+
label="Date"
|
|
166
|
+
format-pattern=""
|
|
167
|
+
></UDExDatePicker>,
|
|
168
|
+
);
|
|
169
|
+
|
|
170
|
+
// Sanity check: the attribute is truly the empty string on the host, so the property
|
|
171
|
+
// is falsy and the fallback branch of `_effectivePlaceholder` is what's being exercised.
|
|
172
|
+
cy.get("#emptyPatternDatePicker").should("have.attr", "format-pattern", "");
|
|
173
|
+
cy.get("#emptyPatternDatePicker").then($el => {
|
|
174
|
+
const dp = $el[0] as unknown as { formatPattern: string };
|
|
175
|
+
expect(dp.formatPattern).to.equal("");
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
cy.get("#emptyPatternDatePicker")
|
|
179
|
+
.shadow()
|
|
180
|
+
.find("[udex-text-field]")
|
|
181
|
+
.shadow()
|
|
182
|
+
.find("input")
|
|
183
|
+
.focus();
|
|
184
|
+
|
|
185
|
+
cy.get("#emptyPatternDatePicker")
|
|
186
|
+
.shadow()
|
|
187
|
+
.find("[udex-text-field]")
|
|
188
|
+
.shadow()
|
|
189
|
+
.find("input")
|
|
190
|
+
.should("have.attr", "placeholder", "MMM dd, yyyy");
|
|
191
|
+
});
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
describe("calendar timestamp navigation", () => {
|
|
195
|
+
// Regression coverage for the fix that forwards `_calendarTimestamp` to the inner
|
|
196
|
+
// `udex-calendar`. UI5's Calendar guards `timestamp` behind an internal
|
|
197
|
+
// `_valueIsProcessed` flag after first render — without this forwarding, the calendar
|
|
198
|
+
// stays on the initial (empty) month when the popover opens after a date is typed.
|
|
199
|
+
// The visual month is a CSS/render concern, so these tests assert the JS-observable
|
|
200
|
+
// contract: the getter and the property flow-through to the child element.
|
|
201
|
+
|
|
202
|
+
it("_calendarTimestamp returns a non-zero UTC seconds timestamp for a valid value", () => {
|
|
203
|
+
cy.mount(
|
|
204
|
+
<UDExDatePicker id="tsDatePicker" value="May 16, 2026"></UDExDatePicker>,
|
|
205
|
+
);
|
|
206
|
+
|
|
207
|
+
cy.get("#tsDatePicker").then($el => {
|
|
208
|
+
const dp = $el[0] as unknown as { _calendarTimestamp: number | undefined };
|
|
209
|
+
expect(dp._calendarTimestamp).to.be.a("number");
|
|
210
|
+
expect(dp._calendarTimestamp).to.be.greaterThan(0);
|
|
211
|
+
});
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
it("_calendarTimestamp is undefined when value is empty", () => {
|
|
215
|
+
cy.mount(<UDExDatePicker id="emptyTsDatePicker"></UDExDatePicker>);
|
|
216
|
+
|
|
217
|
+
cy.get("#emptyTsDatePicker").then($el => {
|
|
218
|
+
const dp = $el[0] as unknown as { _calendarTimestamp: number | undefined };
|
|
219
|
+
// `_extractFirstTimestamp` returns undefined for empty/invalid values.
|
|
220
|
+
// eslint-disable-next-line no-unused-expressions
|
|
221
|
+
expect(dp._calendarTimestamp).to.be.undefined;
|
|
222
|
+
});
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
it("forwards _calendarTimestamp as the `timestamp` property on the inner udex-calendar", () => {
|
|
226
|
+
cy.mount(
|
|
227
|
+
<UDExDatePicker
|
|
228
|
+
id="forwardTsDatePicker"
|
|
229
|
+
value="May 16, 2026"
|
|
230
|
+
></UDExDatePicker>,
|
|
231
|
+
);
|
|
232
|
+
|
|
233
|
+
// Open the popover via the public API so the calendar is rendered into the tree.
|
|
234
|
+
// Avoids coupling to the icon-button shadow DOM shape of the inner TextField.
|
|
235
|
+
cy.get("#forwardTsDatePicker").then($el => {
|
|
236
|
+
($el[0] as unknown as { showCalendar: () => void }).showCalendar();
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
// Read the DatePicker's `_calendarTimestamp` first, then assert the inner
|
|
240
|
+
// udex-calendar's `timestamp` property matches.
|
|
241
|
+
cy.get("#forwardTsDatePicker").then($el => {
|
|
242
|
+
const dp = $el[0] as unknown as { _calendarTimestamp: number | undefined };
|
|
243
|
+
const expected = dp._calendarTimestamp;
|
|
244
|
+
expect(expected, "DatePicker should expose a numeric _calendarTimestamp").to.be.a(
|
|
245
|
+
"number",
|
|
246
|
+
);
|
|
247
|
+
|
|
248
|
+
cy.get("#forwardTsDatePicker")
|
|
249
|
+
.shadow()
|
|
250
|
+
.find("udex-calendar")
|
|
251
|
+
.should("exist")
|
|
252
|
+
.then($cal => {
|
|
253
|
+
const cal = $cal[0] as unknown as { timestamp: number | undefined };
|
|
254
|
+
expect(cal.timestamp).to.equal(expected);
|
|
255
|
+
});
|
|
256
|
+
});
|
|
257
|
+
});
|
|
258
|
+
});
|
|
259
|
+
});
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import {
|
|
2
|
+
UDExMastheadItem,
|
|
3
|
+
UDExMastheadMenu,
|
|
4
|
+
UDExMastheadMobileMenu,
|
|
5
|
+
} from "../support/component-imports.js";
|
|
6
|
+
import { subItemsProduct } from "../fixtures/masthead-shell.js";
|
|
7
|
+
|
|
8
|
+
describe("Masthead item dropdown scroll behavior", () => {
|
|
9
|
+
beforeEach(() => {
|
|
10
|
+
cy.viewport(800, 900);
|
|
11
|
+
cy.mount(
|
|
12
|
+
<UDExMastheadItem id="test-item" accessible-name="Products">
|
|
13
|
+
Products
|
|
14
|
+
<UDExMastheadMenu
|
|
15
|
+
id="test-menu"
|
|
16
|
+
slot="dropdown"
|
|
17
|
+
menu-title="Menu"
|
|
18
|
+
subItems={JSON.stringify(subItemsProduct)}
|
|
19
|
+
/>
|
|
20
|
+
</UDExMastheadItem>,
|
|
21
|
+
);
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it("scrolls dropdown-holder to top when item is deselected on mobile", () => {
|
|
25
|
+
cy.get("#test-item").then($item => {
|
|
26
|
+
($item[0] as any).selected = true;
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
cy.get("#test-item")
|
|
30
|
+
.shadow()
|
|
31
|
+
.find(".udex-masthead-item__dropdown-holder")
|
|
32
|
+
.should("exist");
|
|
33
|
+
|
|
34
|
+
cy.get("#test-item").then($item => {
|
|
35
|
+
const item = $item[0] as any;
|
|
36
|
+
const holder = item.shadowRoot.querySelector(
|
|
37
|
+
".udex-masthead-item__dropdown-holder",
|
|
38
|
+
) as HTMLElement;
|
|
39
|
+
const scrollToSpy = cy.spy(holder, "scrollTo");
|
|
40
|
+
holder.scrollTop = 200;
|
|
41
|
+
|
|
42
|
+
item.selected = false;
|
|
43
|
+
|
|
44
|
+
expect(scrollToSpy).to.have.been.calledOnce;
|
|
45
|
+
expect(scrollToSpy).to.have.been.calledWith({ top: 0 });
|
|
46
|
+
});
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it("does not scroll when item is deselected on desktop", () => {
|
|
50
|
+
cy.viewport(1920, 1080);
|
|
51
|
+
|
|
52
|
+
cy.get("#test-item").then($item => {
|
|
53
|
+
const item = $item[0] as any;
|
|
54
|
+
const scrollSpy = cy.spy(item, "dropdownScrollToTop");
|
|
55
|
+
|
|
56
|
+
item.selected = true;
|
|
57
|
+
item.selected = false;
|
|
58
|
+
|
|
59
|
+
expect(scrollSpy).to.not.have.been.called;
|
|
60
|
+
});
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it("calls mobileMenuScrollToTop on the slotted mobile menu when deselected on mobile", () => {
|
|
64
|
+
cy.viewport(800, 900);
|
|
65
|
+
|
|
66
|
+
cy.mount(
|
|
67
|
+
<UDExMastheadItem id="menu-btn-item" accessible-name="menu" icon="menu2">
|
|
68
|
+
<UDExMastheadMobileMenu id="mobile-menu" slot="dropdown" />
|
|
69
|
+
</UDExMastheadItem>,
|
|
70
|
+
);
|
|
71
|
+
|
|
72
|
+
cy.get("#menu-btn-item").invoke("attr", "_is-menu-open-btn", "");
|
|
73
|
+
|
|
74
|
+
cy.get("#menu-btn-item").then($item => {
|
|
75
|
+
($item[0] as any).selected = true;
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
cy.get("#mobile-menu").then($menu => {
|
|
79
|
+
const mobileMenu = $menu[0] as any;
|
|
80
|
+
const scrollSpy = cy.spy(mobileMenu, "mobileMenuScrollToTop");
|
|
81
|
+
|
|
82
|
+
const item = document.querySelector<any>("#menu-btn-item");
|
|
83
|
+
item.selected = false;
|
|
84
|
+
|
|
85
|
+
expect(scrollSpy).to.have.been.calledOnce;
|
|
86
|
+
});
|
|
87
|
+
});
|
|
88
|
+
});
|
|
@@ -577,7 +577,9 @@ describe("MastheadShell Default component", () => {
|
|
|
577
577
|
});
|
|
578
578
|
|
|
579
579
|
it("'ui5-profile-click' emits when clicking profile item", () => {
|
|
580
|
-
cy.get("#profile-item").
|
|
580
|
+
cy.get("#profile-item").then($el => {
|
|
581
|
+
$el[0].dispatchEvent(new CustomEvent("click-default", { bubbles: true, composed: true }));
|
|
582
|
+
});
|
|
581
583
|
cy.get("#eventComp").should("have.text", "ui5-profile-click");
|
|
582
584
|
});
|
|
583
585
|
|
|
@@ -106,4 +106,108 @@ describe("TagGroup", () => {
|
|
|
106
106
|
cy.get("[udex-tag-group]").shadow().find("[udex-tooltip]").should("have.attr", "accessible-name", "Hidden tags");
|
|
107
107
|
});
|
|
108
108
|
|
|
109
|
+
describe("Keyboard focus with hidden tags", () => {
|
|
110
|
+
it("keeps the 'n more' button visible when a visible tag receives focus (overflow case)", () => {
|
|
111
|
+
cy.mount(<div><button id="before">before</button>{defaultTagGroup}</div>);
|
|
112
|
+
|
|
113
|
+
cy.get("[udex-tag-group]").shadow().find(".udex-tag-group__tokenizer").as("tokenizer");
|
|
114
|
+
// Wait until TagGroup has processed overflow state before navigating
|
|
115
|
+
cy.get("[udex-tag-group]").should("have.attr", "_is-hidden-tags");
|
|
116
|
+
|
|
117
|
+
cy.get("#before").focus();
|
|
118
|
+
cy.realPress("Tab");
|
|
119
|
+
|
|
120
|
+
cy.get("@tokenizer").should("not.have.attr", "expanded");
|
|
121
|
+
cy.get("@tokenizer").shadow().find(".ui5-tokenizer-more-text").should("be.visible");
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
it("keeps the 'n more' button reachable across successive focus moves between tags", () => {
|
|
125
|
+
cy.mount(<div><button id="before">before</button>{defaultTagGroup}</div>);
|
|
126
|
+
|
|
127
|
+
cy.get("[udex-tag-group]").shadow().find(".udex-tag-group__tokenizer").as("tokenizer");
|
|
128
|
+
cy.get("[udex-tag-group]").should("have.attr", "_is-hidden-tags");
|
|
129
|
+
|
|
130
|
+
cy.get("#before").focus();
|
|
131
|
+
cy.realPress("Tab");
|
|
132
|
+
cy.get("@tokenizer").should("not.have.attr", "expanded");
|
|
133
|
+
|
|
134
|
+
cy.realPress("Tab");
|
|
135
|
+
cy.get("@tokenizer").should("not.have.attr", "expanded");
|
|
136
|
+
cy.get("@tokenizer").shadow().find(".ui5-tokenizer-more-text").should("be.visible");
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
it("does not collapse the tokenizer on focusin when there are no hidden tags (no regression)", () => {
|
|
140
|
+
cy.mount(<div><button id="before">before</button>{expandedTagGroup}</div>);
|
|
141
|
+
|
|
142
|
+
cy.get("[udex-tag-group]").shadow().find(".udex-tag-group__tokenizer").as("tokenizer");
|
|
143
|
+
cy.get("@tokenizer").should("have.attr", "expanded");
|
|
144
|
+
|
|
145
|
+
cy.get("#before").focus();
|
|
146
|
+
cy.realPress("Tab");
|
|
147
|
+
|
|
148
|
+
cy.get("@tokenizer").should("have.attr", "expanded");
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
it("Tab on the last visible tag moves focus to the 'n more' button (overflow case)", () => {
|
|
152
|
+
cy.mount(<div><button id="before">before</button>{defaultTagGroup}</div>);
|
|
153
|
+
|
|
154
|
+
cy.get("[udex-tag-group]").shadow().find(".udex-tag-group__tokenizer").as("tokenizer");
|
|
155
|
+
cy.get("[udex-tag-group]").should("have.attr", "_is-hidden-tags");
|
|
156
|
+
cy.get("@tokenizer").shadow().find(".ui5-tokenizer-more-text").should("be.visible");
|
|
157
|
+
|
|
158
|
+
cy.get("@tokenizer").then($tokenizer => {
|
|
159
|
+
const nMoreCount = ($tokenizer[0] as any)._nMoreCount as number;
|
|
160
|
+
const totalCount = ($tokenizer[0] as any)._tokens.length as number;
|
|
161
|
+
const visibleCount = totalCount - nMoreCount;
|
|
162
|
+
cy.get("#before").focus();
|
|
163
|
+
// Tab to enter group (1) + Tab through each visible tag to reach the last
|
|
164
|
+
Cypress._.times(visibleCount, () => cy.realPress("Tab"));
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
cy.realPress("Tab");
|
|
168
|
+
|
|
169
|
+
cy.get("@tokenizer").shadow().find(".ui5-tokenizer-more-text").should("be.focused");
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
it("Shift+Tab on the 'n more' button moves focus back to the last visible tag", () => {
|
|
173
|
+
cy.mount(<div><button id="before">before</button>{defaultTagGroup}</div>);
|
|
174
|
+
|
|
175
|
+
cy.get("[udex-tag-group]").shadow().find(".udex-tag-group__tokenizer").as("tokenizer");
|
|
176
|
+
cy.get("[udex-tag-group]").should("have.attr", "_is-hidden-tags");
|
|
177
|
+
|
|
178
|
+
cy.get("@tokenizer").then($tokenizer => {
|
|
179
|
+
const nMoreCount = ($tokenizer[0] as any)._nMoreCount as number;
|
|
180
|
+
const totalCount = ($tokenizer[0] as any)._tokens.length as number;
|
|
181
|
+
const visibleCount = totalCount - nMoreCount;
|
|
182
|
+
cy.get("#before").focus();
|
|
183
|
+
// Tab through all visible tags + 1 to land on "n more"
|
|
184
|
+
Cypress._.times(visibleCount + 1, () => cy.realPress("Tab"));
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
cy.get("@tokenizer").shadow().find(".ui5-tokenizer-more-text").should("be.focused");
|
|
188
|
+
|
|
189
|
+
cy.realPress(["Shift", "Tab"]);
|
|
190
|
+
|
|
191
|
+
cy.get("@tokenizer").then($tokenizer => {
|
|
192
|
+
const nMoreCount = ($tokenizer[0] as any)._nMoreCount as number;
|
|
193
|
+
const totalCount = ($tokenizer[0] as any)._tokens.length as number;
|
|
194
|
+
const lastVisibleToken = ($tokenizer[0] as any)._tokens[totalCount - nMoreCount - 1];
|
|
195
|
+
cy.wrap(lastVisibleToken).shadow().find("a, [tabindex='0']").first().should("be.focused");
|
|
196
|
+
});
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
it("keeps the 'n more' button present in shadow DOM whenever a visible tag is focused", () => {
|
|
200
|
+
cy.mount(<div><button id="before">before</button>{defaultTagGroup}</div>);
|
|
201
|
+
|
|
202
|
+
cy.get("[udex-tag-group]").shadow().find(".udex-tag-group__tokenizer").as("tokenizer");
|
|
203
|
+
cy.get("[udex-tag-group]").should("have.attr", "_is-hidden-tags");
|
|
204
|
+
|
|
205
|
+
cy.get("#before").focus();
|
|
206
|
+
cy.realPress("Tab");
|
|
207
|
+
|
|
208
|
+
cy.get("@tokenizer").should("not.have.attr", "expanded");
|
|
209
|
+
cy.get("@tokenizer").shadow().find(".ui5-tokenizer-more-text").should("exist").and("be.visible");
|
|
210
|
+
});
|
|
211
|
+
});
|
|
212
|
+
|
|
109
213
|
});
|
|
@@ -103,7 +103,7 @@ describe("Text Field", () => {
|
|
|
103
103
|
});
|
|
104
104
|
|
|
105
105
|
it("Supporting text span doesn't appear by default", () => {
|
|
106
|
-
cy.get("#defaultTextField").shadow().
|
|
106
|
+
cy.get("#defaultTextField").shadow().find("span").should("not.exist");
|
|
107
107
|
});
|
|
108
108
|
|
|
109
109
|
it("\"aria-describedby\" attribute is set", () => {
|
|
@@ -138,6 +138,69 @@ describe("Text Field", () => {
|
|
|
138
138
|
})
|
|
139
139
|
});
|
|
140
140
|
|
|
141
|
+
describe("placeholder", () => {
|
|
142
|
+
// Updated: placeholder now only renders when the field is active (focused) so
|
|
143
|
+
// it does not visually overlap the floating label when the field is empty and blurred.
|
|
144
|
+
it("does not render a non-empty placeholder on the native input by default", () => {
|
|
145
|
+
cy.get("#defaultTextField").shadow().find("input").then($input => {
|
|
146
|
+
const placeholder = $input.attr("placeholder");
|
|
147
|
+
// Placeholder attribute may be absent or present as empty string when unset
|
|
148
|
+
expect(placeholder === undefined || placeholder === "").to.equal(true);
|
|
149
|
+
});
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
// Updated: placeholder is now gated on `active`. Assert absent before focus, present after focus.
|
|
153
|
+
it("does not render the placeholder on the native input until the field is active (focused)", () => {
|
|
154
|
+
const PLACEHOLDER_VALUE = "Enter your name";
|
|
155
|
+
cy.mount(<UDExTextField placeholder={PLACEHOLDER_VALUE} label="Name" id="placeholderTextField"></UDExTextField>);
|
|
156
|
+
|
|
157
|
+
// Before focus: placeholder should not be present (attribute absent or empty).
|
|
158
|
+
cy.get("#placeholderTextField").shadow().find("input").then($input => {
|
|
159
|
+
const placeholder = $input.attr("placeholder");
|
|
160
|
+
expect(placeholder === undefined || placeholder === "").to.equal(true);
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
// Focus the native input to switch the field to active state.
|
|
164
|
+
cy.get("#placeholderTextField").shadow().find("input").focus();
|
|
165
|
+
|
|
166
|
+
// After focus: placeholder should be rendered on the native input.
|
|
167
|
+
cy.get("#placeholderTextField").shadow().find("input")
|
|
168
|
+
.should("have.attr", "placeholder", PLACEHOLDER_VALUE);
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
// Updated: when no label is present, there is nothing to overlap the placeholder area,
|
|
172
|
+
// so the placeholder is rendered on the native input at all times (not gated on `active`).
|
|
173
|
+
it("renders the placeholder on the native input without focus when no label is set", () => {
|
|
174
|
+
const PLACEHOLDER_VALUE = "Enter your name";
|
|
175
|
+
cy.mount(
|
|
176
|
+
<UDExTextField placeholder={PLACEHOLDER_VALUE} id="noLabelPlaceholderTextField"></UDExTextField>,
|
|
177
|
+
);
|
|
178
|
+
|
|
179
|
+
// No label => `active || !this.label` is true even before focus.
|
|
180
|
+
cy.get("#noLabelPlaceholderTextField").shadow().find("input")
|
|
181
|
+
.should("have.attr", "placeholder", PLACEHOLDER_VALUE);
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
// Updated: propagation now only observable while the field is active — focus, then assert reactive update.
|
|
185
|
+
it("updates the native input placeholder when the property changes (while active)", () => {
|
|
186
|
+
cy.mount(<UDExTextField placeholder="Original" id="reactivePlaceholder"></UDExTextField>);
|
|
187
|
+
|
|
188
|
+
// Focus first so the placeholder is actually rendered on the native input.
|
|
189
|
+
cy.get("#reactivePlaceholder").shadow().find("input").focus();
|
|
190
|
+
cy.get("#reactivePlaceholder").shadow().find("input")
|
|
191
|
+
.should("have.attr", "placeholder", "Original");
|
|
192
|
+
|
|
193
|
+
cy.get("#reactivePlaceholder").then($el => {
|
|
194
|
+
($el[0] as unknown as { placeholder: string }).placeholder = "Updated";
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
// Keep focus on the input so `active` stays true, and assert the new value is reflected.
|
|
198
|
+
cy.get("#reactivePlaceholder").shadow().find("input").focus();
|
|
199
|
+
cy.get("#reactivePlaceholder").shadow().find("input")
|
|
200
|
+
.should("have.attr", "placeholder", "Updated");
|
|
201
|
+
});
|
|
202
|
+
});
|
|
203
|
+
|
|
141
204
|
it("sets object fieldRef current to input element inside shadow root", () => {
|
|
142
205
|
const fieldRefObj = { current: null as HTMLInputElement | null };
|
|
143
206
|
cy.mount(<UDExTextField fieldRef={fieldRefObj}></UDExTextField>);
|
|
@@ -11,6 +11,7 @@ import UDExCheckbox from "../../instrumented/Checkbox.js";
|
|
|
11
11
|
import UDExContact from "../../instrumented/Contact.js";
|
|
12
12
|
import UDExContactItem from "../../instrumented/ContactItem.js";
|
|
13
13
|
import UDExCountrySelector from "../../instrumented/CountrySelector.js";
|
|
14
|
+
import UDExDatePicker from "../../instrumented/DatePicker.js";
|
|
14
15
|
import UDExDivider from "../../instrumented/Divider.js";
|
|
15
16
|
import UDExDropdown from "../../instrumented/Dropdown.js";
|
|
16
17
|
import UDExEventRibbon from "../../instrumented/EventRibbon.js";
|
|
@@ -99,6 +100,7 @@ export {
|
|
|
99
100
|
UDExContact,
|
|
100
101
|
UDExContactItem,
|
|
101
102
|
UDExCountrySelector,
|
|
103
|
+
UDExDatePicker,
|
|
102
104
|
UDExDivider,
|
|
103
105
|
UDExDropdown,
|
|
104
106
|
UDExEventRibbon,
|
|
@@ -11,6 +11,7 @@ import UDExCheckbox from "../../src/Checkbox.js";
|
|
|
11
11
|
import UDExContact from "../../src/Contact.js";
|
|
12
12
|
import UDExContactItem from "../../src/ContactItem.js";
|
|
13
13
|
import UDExCountrySelector from "../../src/CountrySelector.js";
|
|
14
|
+
import UDExDatePicker from "../../src/DatePicker.js";
|
|
14
15
|
import UDExDivider from "../../src/Divider.js";
|
|
15
16
|
import UDExDropdown from "../../src/Dropdown.js";
|
|
16
17
|
import UDExEventRibbon from "../../src/EventRibbon.js";
|
|
@@ -99,6 +100,7 @@ export {
|
|
|
99
100
|
UDExContact,
|
|
100
101
|
UDExContactItem,
|
|
101
102
|
UDExCountrySelector,
|
|
103
|
+
UDExDatePicker,
|
|
102
104
|
UDExDivider,
|
|
103
105
|
UDExDropdown,
|
|
104
106
|
UDExEventRibbon,
|
package/dist/Carousel.d.ts
CHANGED
|
@@ -2,6 +2,7 @@ import UI5Element from "@ui5/webcomponents-base/dist/UI5Element.js";
|
|
|
2
2
|
import type { DefaultSlot } from "@ui5/webcomponents-base/dist/UI5Element.js";
|
|
3
3
|
import type I18nBundle from "@ui5/webcomponents-base/dist/i18nBundle.js";
|
|
4
4
|
import type UI5Carousel from "@ui5/webcomponents/dist/Carousel.js";
|
|
5
|
+
import type { CarouselNavigateEventDetail } from "@ui5/webcomponents/dist/Carousel.js";
|
|
5
6
|
import type { UI5CustomEvent } from "@ui5/webcomponents-base";
|
|
6
7
|
import type CarouselPageIndicatorType from "@ui5/webcomponents/dist/types/CarouselPageIndicatorType.js";
|
|
7
8
|
import type UDExButton from "./Button.js";
|
|
@@ -32,6 +33,9 @@ export declare enum UDExCarouselButtonDesign {
|
|
|
32
33
|
* @public
|
|
33
34
|
*/
|
|
34
35
|
declare class UDExCarousel extends UI5Element {
|
|
36
|
+
eventDetails: {
|
|
37
|
+
"navigate": CarouselNavigateEventDetail;
|
|
38
|
+
};
|
|
35
39
|
/**
|
|
36
40
|
* Defines the position of the navigation.
|
|
37
41
|
* <br><br>
|
|
@@ -112,7 +116,7 @@ declare class UDExCarousel extends UI5Element {
|
|
|
112
116
|
_prevButtonClick(e: UI5CustomEvent<UDExButton, "click">): void;
|
|
113
117
|
_nextButtonClick(e: UI5CustomEvent<UDExButton, "click">): void;
|
|
114
118
|
_onkeydown(e: KeyboardEvent): void;
|
|
115
|
-
_handleNavigate(): void;
|
|
119
|
+
_handleNavigate(e: UI5CustomEvent<UI5Carousel, "navigate">): void;
|
|
116
120
|
onAfterRendering(): void;
|
|
117
121
|
}
|
|
118
122
|
export default UDExCarousel;
|
package/dist/Carousel.js
CHANGED
|
@@ -9,6 +9,7 @@ import UI5Element from "@ui5/webcomponents-base/dist/UI5Element.js";
|
|
|
9
9
|
import customElement from "@ui5/webcomponents-base/dist/decorators/customElement.js";
|
|
10
10
|
import property from "@ui5/webcomponents-base/dist/decorators/property.js";
|
|
11
11
|
import slot from "@ui5/webcomponents-base/dist/decorators/slot.js";
|
|
12
|
+
import event from "@ui5/webcomponents-base/dist/decorators/event-strict.js";
|
|
12
13
|
import jsxRenderer from "@ui5/webcomponents-base/renderer/JsxRenderer.js";
|
|
13
14
|
import i18n from "@ui5/webcomponents-base/dist/decorators/i18n.js";
|
|
14
15
|
import { isUp, isDown, isRight, isLeft, } from "@ui5/webcomponents-base/dist/Keys.js";
|
|
@@ -144,8 +145,9 @@ let UDExCarousel = UDExCarousel_1 = class UDExCarousel extends UI5Element {
|
|
|
144
145
|
this._calculateDisabledButtonsState();
|
|
145
146
|
}
|
|
146
147
|
}
|
|
147
|
-
_handleNavigate() {
|
|
148
|
+
_handleNavigate(e) {
|
|
148
149
|
this._calculateDisabledButtonsState();
|
|
150
|
+
this.fireDecoratorEvent("navigate", e.detail);
|
|
149
151
|
}
|
|
150
152
|
onAfterRendering() {
|
|
151
153
|
redirectAttributes(this, "carousel");
|
|
@@ -183,6 +185,13 @@ UDExCarousel = UDExCarousel_1 = __decorate([
|
|
|
183
185
|
styles: CarouselCss,
|
|
184
186
|
template: CarouselTemplate,
|
|
185
187
|
})
|
|
188
|
+
/**
|
|
189
|
+
* Triggered when carousel navigates to a new slide
|
|
190
|
+
* @public
|
|
191
|
+
* @param { CustomEvent }
|
|
192
|
+
*/
|
|
193
|
+
,
|
|
194
|
+
event("navigate")
|
|
186
195
|
], UDExCarousel);
|
|
187
196
|
UDExCarousel.define();
|
|
188
197
|
export default UDExCarousel;
|