@celestia-island/hikari 0.43.1 → 0.43.3
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/package.json +1 -1
- package/src/components/HkSelect.scss +27 -1
- package/src/components/HkSelectPanel.tsx +40 -1
- package/src/components/HkTagInput.scss +84 -1
- package/src/components/HkTagInput.singleScroll.contract.test.ts +78 -5
- package/src/components/HkTagInput.test.tsx +813 -9
- package/src/components/HkTagInput.tsx +459 -109
- package/src/components/HkThemeToggle.contract.test.ts +16 -1
- package/src/components/HkThemeToggle.scss +16 -9
- package/src/composables/usePointerReorder.test.ts +651 -0
- package/src/composables/usePointerReorder.ts +432 -0
- package/src/i18n/locales/ar/components.json +2 -1
- package/src/i18n/locales/de/components.json +2 -1
- package/src/i18n/locales/en/components.json +2 -1
- package/src/i18n/locales/es/components.json +2 -1
- package/src/i18n/locales/fr/components.json +2 -1
- package/src/i18n/locales/ja/components.json +2 -1
- package/src/i18n/locales/ko/components.json +2 -1
- package/src/i18n/locales/pt/components.json +2 -1
- package/src/i18n/locales/ru/components.json +2 -1
- package/src/i18n/locales/zh-Hans/components.json +2 -1
- package/src/i18n/locales/zh-Hant/components.json +2 -1
package/package.json
CHANGED
|
@@ -177,9 +177,24 @@
|
|
|
177
177
|
/* Menu/switcher panels overflow a bare 240px cap even on tall viewports
|
|
178
178
|
(user feedback 2026-08-30: identity header + rows ≈ 320px+). Keep 240px
|
|
179
179
|
as the floor for select lists on short viewports, otherwise let the
|
|
180
|
-
panel size to its content up to a viewport-relative ceiling
|
|
180
|
+
panel size to its content up to a viewport-relative ceiling — plain-vh
|
|
181
|
+
first, dvh second, so an engine without dvh drops the dvh twin at PARSE
|
|
182
|
+
time and keeps the vh cap (the HkModal/HkDrawer/HkImageLightbox rule).
|
|
183
|
+
A consumer that needs a tighter surface (HkTagInput's catalog) publishes
|
|
184
|
+
--hk-select-panel-max-height through HkSelectPanel's `maxHeight` prop;
|
|
185
|
+
that hook rides the dvh-capable branch below, because a dvh value
|
|
186
|
+
reaching a dvh-less engine through var() would not be parse-dropped but
|
|
187
|
+
invalid at COMPUTED-value time — which resolves to `max-height: none`,
|
|
188
|
+
losing the cap entirely. Such an engine keeps the plain-vh ceiling
|
|
189
|
+
instead, exactly as it does today. */
|
|
181
190
|
max-height: max(240px, min(36rem, calc(100vh - 32px)));
|
|
182
191
|
max-height: max(240px, min(36rem, calc(100dvh - 32px)));
|
|
192
|
+
@supports (height: 1dvh) {
|
|
193
|
+
max-height: var(
|
|
194
|
+
--hk-select-panel-max-height,
|
|
195
|
+
max(240px, min(36rem, calc(100dvh - 32px)))
|
|
196
|
+
);
|
|
197
|
+
}
|
|
183
198
|
display: flex;
|
|
184
199
|
flex-direction: column;
|
|
185
200
|
gap: var(--space-2);
|
|
@@ -371,6 +386,17 @@
|
|
|
371
386
|
min-height: 0;
|
|
372
387
|
overflow-y: auto;
|
|
373
388
|
overscroll-behavior: contain;
|
|
389
|
+
/* The same consumer-tunable cap the popout reads, applied to the band
|
|
390
|
+
that actually scrolls: the sheet header keeps its place and the
|
|
391
|
+
catalog scrolls inside it. `none` (the fallback) is exactly today's
|
|
392
|
+
behaviour, so an untouched consumer's sheet is unchanged.
|
|
393
|
+
DELIBERATELY not wrapped in the popout's `@supports (height: 1dvh)`
|
|
394
|
+
guard: this band's fallback already IS the property's initial value,
|
|
395
|
+
so a value the engine cannot use resolves to the same `none` the
|
|
396
|
+
guard would produce — the asymmetry is a no-op, not an oversight.
|
|
397
|
+
The sheet PANEL above keeps its own plain-vh/dvh family cap either
|
|
398
|
+
way, so a hidden cap can never leave the sheet unbounded. */
|
|
399
|
+
max-height: var(--hk-select-panel-max-height, none);
|
|
374
400
|
/* Side insets so the option blocks (full-row hover pills) read as
|
|
375
401
|
cards inside the sheet instead of an edge-to-edge strip glued to
|
|
376
402
|
both screen sides — matching the popover family's breathing room.
|
|
@@ -47,6 +47,15 @@ import "./HkSelect.scss";
|
|
|
47
47
|
* events on the panel surface are forwarded (`keydown`) so owners that own
|
|
48
48
|
* an option model (like HkSelect) can run their own arrow/enter navigation.
|
|
49
49
|
*
|
|
50
|
+
* Geometry is the surface's to decide: `matchAnchorWidth` (default true,
|
|
51
|
+
* select parity) ties the popout's minimum width to the trigger, and the
|
|
52
|
+
* optional `maxHeight` caps whichever element scrolls (the desktop popout,
|
|
53
|
+
* the mobile sheet's list band). Both are opt-outs — a consumer that passes
|
|
54
|
+
* neither gets the historic panel, which is what every existing caller
|
|
55
|
+
* does; a consumer whose content should scroll inside a hugged surface
|
|
56
|
+
* (a tag catalog, a long filter list) sets them and relies on the panel's
|
|
57
|
+
* ONE scrollbar.
|
|
58
|
+
*
|
|
50
59
|
* Mobile sheets also run a duplicate-title filter: content that opens with
|
|
51
60
|
* a non-interactive heading exactly repeating the panel `title` is hidden
|
|
52
61
|
* (`.hk-sheet-dup-title`) — the sheet header already names the sheet, and
|
|
@@ -85,6 +94,23 @@ export default defineComponent({
|
|
|
85
94
|
offset: { type: Number, default: 4 },
|
|
86
95
|
/** Popout min-width follows the anchor width (select parity). */
|
|
87
96
|
matchAnchorWidth: { type: Boolean, default: true },
|
|
97
|
+
/** Cap for the surface that scrolls — the desktop popout or the
|
|
98
|
+
* mobile sheet's list band — as ANY CSS length (`min(18rem, 45dvh)`,
|
|
99
|
+
* `24rem`, `calc(50vh - 3rem)`).
|
|
100
|
+
*
|
|
101
|
+
* Undefined (the default) keeps the stylesheet's own ceilings, so
|
|
102
|
+
* every existing consumer is untouched; a consumer whose content
|
|
103
|
+
* should scroll instead of stretching the panel to the viewport sets
|
|
104
|
+
* it, and the panel's ONE scrollbar (never a second region) does the
|
|
105
|
+
* rest. Applied through the `--hk-select-panel-max-height` hook
|
|
106
|
+
* HkSelect.scss reads, inside its `@supports (height: 1dvh)` branch —
|
|
107
|
+
* a value an engine cannot use is therefore IGNORED rather than
|
|
108
|
+
* clamped: the popout keeps the plain-vh ceiling it has today.
|
|
109
|
+
* A MALFORMED value (a typo, a bare number) is likewise not sanitized:
|
|
110
|
+
* the substituted declaration becomes invalid at computed-value time
|
|
111
|
+
* and resolves to `max-height: none`, i.e. the surface is UNCAPPED —
|
|
112
|
+
* the type gate is a CSS length, the value gate is the consumer's. */
|
|
113
|
+
maxHeight: { type: String, default: undefined },
|
|
88
114
|
/** Dock as a bottom sheet on phone-width viewports. */
|
|
89
115
|
sheetOnMobile: { type: Boolean, default: true },
|
|
90
116
|
},
|
|
@@ -320,6 +346,14 @@ export default defineComponent({
|
|
|
320
346
|
const sheetBodyRef = ref<HTMLElement>();
|
|
321
347
|
const coords = ref<{ top?: string; left?: string; minWidth?: string }>({});
|
|
322
348
|
|
|
349
|
+
/** Host-tunable surface cap (the `maxHeight` prop): published as the
|
|
350
|
+
* custom property HkSelect.scss reads on whichever element scrolls,
|
|
351
|
+
* so the stylesheet keeps today's ceiling as its fallback and a
|
|
352
|
+
* consumer that passes nothing renders exactly as before. */
|
|
353
|
+
const surfaceCap = computed<Record<string, string> | undefined>(() =>
|
|
354
|
+
props.maxHeight ? { "--hk-select-panel-max-height": props.maxHeight } : undefined,
|
|
355
|
+
);
|
|
356
|
+
|
|
323
357
|
// ── overlay scrollbar (shared chrome) ─────────────────────────
|
|
324
358
|
// Attached per open on whichever surface scrolls — the desktop
|
|
325
359
|
// popout or the mobile sheet list — and detached on close/unmount
|
|
@@ -632,7 +666,11 @@ export default defineComponent({
|
|
|
632
666
|
</HIconButton>
|
|
633
667
|
</div>
|
|
634
668
|
<div class="hk-select-sheet-body" ref={sheetBodyRef}>
|
|
635
|
-
<div
|
|
669
|
+
<div
|
|
670
|
+
class="hk-select-sheet-list"
|
|
671
|
+
ref={sheetListRef}
|
|
672
|
+
style={surfaceCap.value}
|
|
673
|
+
>
|
|
636
674
|
<div class="hk-select-sheet-content" ref={sheetContentRef}>
|
|
637
675
|
{slots.default?.()}
|
|
638
676
|
</div>
|
|
@@ -663,6 +701,7 @@ export default defineComponent({
|
|
|
663
701
|
<div
|
|
664
702
|
ref={panelRef}
|
|
665
703
|
class="hk-select-popout"
|
|
704
|
+
style={surfaceCap.value}
|
|
666
705
|
aria-label={props.title || undefined}
|
|
667
706
|
onKeydown={forwardKeydown}
|
|
668
707
|
>
|
|
@@ -3,7 +3,10 @@
|
|
|
3
3
|
* HkInput's box (border, radius, rhythm, focus ring) so a tag field sits
|
|
4
4
|
* flush beside plain inputs in the same form; the panel chrome mirrors
|
|
5
5
|
* HkAffixPicker's popup (search on top, rows below, NO inner scroll
|
|
6
|
-
* region — the HkSelectPanel window owns the single scrollbar
|
|
6
|
+
* region — the HkSelectPanel window owns the single scrollbar, capped by
|
|
7
|
+
* the component's `maxHeight` so a long catalog scrolls there instead of
|
|
8
|
+
* stretching the panel). Chips and selected rows are reorderable; the
|
|
9
|
+
* drag states (`data-dragging` / `data-drop`) live at the bottom. */
|
|
7
10
|
|
|
8
11
|
.hk-tag-input-wrapper {
|
|
9
12
|
display: block;
|
|
@@ -78,6 +81,15 @@
|
|
|
78
81
|
|
|
79
82
|
.hk-tag-input-tag {
|
|
80
83
|
max-width: 100%;
|
|
84
|
+
/* Reorder affordance: the chip BODY is the handle. */
|
|
85
|
+
cursor: grab;
|
|
86
|
+
/* A press that becomes a drag must not paint a text selection, and a
|
|
87
|
+
* touch that starts scrolling vertically belongs to the page (the
|
|
88
|
+
* browser takes the gesture back with a pointercancel; a horizontal
|
|
89
|
+
* drag is ours — see usePointerReorder). */
|
|
90
|
+
user-select: none;
|
|
91
|
+
-webkit-user-select: none;
|
|
92
|
+
touch-action: pan-y;
|
|
81
93
|
}
|
|
82
94
|
|
|
83
95
|
.hk-tag-input-tag-text {
|
|
@@ -87,6 +99,14 @@
|
|
|
87
99
|
white-space: nowrap;
|
|
88
100
|
}
|
|
89
101
|
|
|
102
|
+
/* A disabled field reorders nothing: the chips drop the grab affordance
|
|
103
|
+
* (and the gesture reservation with it) — the box's own not-allowed
|
|
104
|
+
* cursor takes over. */
|
|
105
|
+
.hk-tag-input-box[data-disabled] .hk-tag-input-tag {
|
|
106
|
+
cursor: default;
|
|
107
|
+
touch-action: auto;
|
|
108
|
+
}
|
|
109
|
+
|
|
90
110
|
/* ── inline typing input / placeholder ────────────────────────────── */
|
|
91
111
|
.hk-tag-input-element {
|
|
92
112
|
/* Auto-growing: the element's width is published inline in `ch`
|
|
@@ -171,6 +191,12 @@
|
|
|
171
191
|
.hk-tag-input-panel {
|
|
172
192
|
display: flex;
|
|
173
193
|
flex-direction: column;
|
|
194
|
+
/* A FIXED measure, not a content-driven one: the popout is positioned
|
|
195
|
+
* once when it opens, so a panel that grew with its content would shift
|
|
196
|
+
* its own left edge mid-interaction — toggling a row adds a check glyph,
|
|
197
|
+
* and a query changes which rows (and how much meta text) survive.
|
|
198
|
+
* min/max stay as the bounds a narrow viewport imposes on that width. */
|
|
199
|
+
width: min(19rem, calc(100vw - 2rem));
|
|
174
200
|
min-width: 13rem;
|
|
175
201
|
max-width: min(19rem, calc(100vw - 2rem));
|
|
176
202
|
}
|
|
@@ -291,6 +317,62 @@
|
|
|
291
317
|
font-variant-numeric: tabular-nums;
|
|
292
318
|
}
|
|
293
319
|
|
|
320
|
+
/* ── reordering ───────────────────────────────────────────────────── */
|
|
321
|
+
/* Live drag (both lists): the item being moved dims, the slot it would
|
|
322
|
+
* land in is ringed. Chips carry the state as classes (HkTag takes no
|
|
323
|
+
* arbitrary attributes), panel rows as data attributes — both are
|
|
324
|
+
* assertable hooks that never collide with the pointer/hover faces. */
|
|
325
|
+
.hk-tag-input-tag-dragging,
|
|
326
|
+
.hk-tag-input-row[data-dragging] {
|
|
327
|
+
opacity: 0.55;
|
|
328
|
+
cursor: grabbing;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
.hk-tag-input-tag-drop {
|
|
332
|
+
outline: 1px dashed rgb(var(--color-focused-border));
|
|
333
|
+
outline-offset: 1px;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
.hk-tag-input-row[data-drop] {
|
|
337
|
+
box-shadow: inset 0 0 0 1px rgb(var(--color-focused-border));
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
/* Drag handle of a SELECTED panel row — the one element that claims the
|
|
341
|
+
* touch gesture outright, so the sheet keeps scrolling under the rest of
|
|
342
|
+
* the row. Only selected rows render one: an unselected row is not
|
|
343
|
+
* draggable and is never a drop target. */
|
|
344
|
+
.hk-tag-input-grip {
|
|
345
|
+
display: inline-flex;
|
|
346
|
+
align-items: center;
|
|
347
|
+
justify-content: center;
|
|
348
|
+
flex: none;
|
|
349
|
+
/* The largest target the row rhythm allows: the row's content box is the
|
|
350
|
+
2.25rem row minus its 4px block padding, so the handle grows to that
|
|
351
|
+
without changing the option rows' height (the 44px touch guidance
|
|
352
|
+
cannot fit in a list row — the sheet scrolls through them instead). */
|
|
353
|
+
width: 1.5rem;
|
|
354
|
+
height: 1.75rem;
|
|
355
|
+
margin-inline-end: -4px;
|
|
356
|
+
color: rgb(var(--color-muted));
|
|
357
|
+
opacity: 0.6;
|
|
358
|
+
cursor: grab;
|
|
359
|
+
touch-action: none;
|
|
360
|
+
user-select: none;
|
|
361
|
+
-webkit-user-select: none;
|
|
362
|
+
transition: opacity 0.12s ease, color 0.12s ease;
|
|
363
|
+
|
|
364
|
+
&:active {
|
|
365
|
+
cursor: grabbing;
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
.hk-tag-input-row:hover .hk-tag-input-grip,
|
|
370
|
+
.hk-tag-input-row[data-dragging] .hk-tag-input-grip,
|
|
371
|
+
.hk-tag-input-grip:focus-visible {
|
|
372
|
+
opacity: 1;
|
|
373
|
+
color: rgb(var(--color-text));
|
|
374
|
+
}
|
|
375
|
+
|
|
294
376
|
.hk-tag-input-empty {
|
|
295
377
|
padding: 14px 12px;
|
|
296
378
|
color: rgb(var(--color-muted));
|
|
@@ -301,6 +383,7 @@
|
|
|
301
383
|
@media (prefers-reduced-motion: reduce) {
|
|
302
384
|
.hk-tag-input-box,
|
|
303
385
|
.hk-tag-input-chevron,
|
|
386
|
+
.hk-tag-input-grip,
|
|
304
387
|
.hk-tag-input-row {
|
|
305
388
|
transition: none;
|
|
306
389
|
}
|
|
@@ -6,6 +6,14 @@
|
|
|
6
6
|
* `max-height: 17rem; overflow: auto` row region. It has none: the
|
|
7
7
|
* HkSelectPanel window surface (desktop popout / mobile sheet) owns THE
|
|
8
8
|
* single scrollbar and scrolls the search field with the rows as one.
|
|
9
|
+
*
|
|
10
|
+
* The 2026-09-11 geometry wave leans on the same contract from the other
|
|
11
|
+
* side: the field used to hand the panel its own width (`matchAnchorWidth`,
|
|
12
|
+
* a full-width settings column) while the catalog caps at 19rem, leaving an
|
|
13
|
+
* empty band and a scrollbar parked far from the rows. The panel now hugs
|
|
14
|
+
* its own measure, and the catalog scrolls because the SURFACE is capped
|
|
15
|
+
* (`maxHeight` → the --hk-select-panel-max-height hook) — never because the
|
|
16
|
+
* row list grew a scroll region of its own.
|
|
9
17
|
*/
|
|
10
18
|
import { describe, expect, it } from "vitest";
|
|
11
19
|
import { readFileSync } from "node:fs";
|
|
@@ -34,6 +42,14 @@ function cssBlock(src: string, selector: string): string {
|
|
|
34
42
|
return "";
|
|
35
43
|
}
|
|
36
44
|
|
|
45
|
+
/** The JSX opening tag that carries `marker` (attributes only, no
|
|
46
|
+
* children) — the pin below is about what ONE element declares. */
|
|
47
|
+
function jsxOpeningTag(src: string, marker: string): string {
|
|
48
|
+
const at = src.indexOf(marker);
|
|
49
|
+
expect(at, `${marker} is rendered`).toBeGreaterThanOrEqual(0);
|
|
50
|
+
return src.slice(src.lastIndexOf("<", at), src.indexOf(">", at) + 1);
|
|
51
|
+
}
|
|
52
|
+
|
|
37
53
|
function expectNoScroll(block: string, what: string): void {
|
|
38
54
|
expect(block, `${what} block exists`).toBeTruthy();
|
|
39
55
|
expect(block).not.toMatch(/max-height/);
|
|
@@ -61,13 +77,70 @@ describe("HkTagInput single-scrollbar-per-window contract", () => {
|
|
|
61
77
|
expectNoScroll(block, ".hk-select-sheet-panel .hk-tag-input-panel");
|
|
62
78
|
});
|
|
63
79
|
|
|
64
|
-
it("the component mounts no overlay scrollbar machinery", () => {
|
|
80
|
+
it("the component mounts no overlay scrollbar machinery and clamps no row region", () => {
|
|
65
81
|
expect(tsx).not.toContain("attachOverlayScrollbars");
|
|
66
82
|
expect(tsx).not.toContain("OverlayScrollbarHandle");
|
|
67
|
-
// No inline clamp on the row list either (the prose in the file
|
|
68
|
-
// mentions `max-height` in the contract comment, so this pins the
|
|
69
|
-
// style property spelling instead of the words).
|
|
70
|
-
expect(tsx).not.toContain("maxHeight");
|
|
71
83
|
expect(tsx).not.toContain("overflowY");
|
|
84
|
+
// The ONLY max-height in the file belongs to the panel SURFACE prop
|
|
85
|
+
// (the very next test): the listbox element itself stays unpinned, so
|
|
86
|
+
// the window keeps the one scroll region.
|
|
87
|
+
const listEl = jsxOpeningTag(tsx, 'class="hk-tag-input-list"');
|
|
88
|
+
expect(listEl.startsWith("<div")).toBe(true);
|
|
89
|
+
expect(listEl).not.toMatch(/style=/);
|
|
90
|
+
expect(listEl).not.toMatch(/maxHeight|max-height|overflow/);
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
it("hugs its own measure, and scrolls by capping the SURFACE (an additive prop)", () => {
|
|
94
|
+
const panel = jsxOpeningTag(tsx, "<HkSelectPanel");
|
|
95
|
+
// The field's width is not the panel's width any more…
|
|
96
|
+
expect(panel).toContain("matchAnchorWidth={false}");
|
|
97
|
+
// …and the catalog scrolls inside a capped surface instead of growing
|
|
98
|
+
// to the stylesheet ceiling.
|
|
99
|
+
expect(panel).toContain('maxHeight="min(18rem, 45dvh)"');
|
|
100
|
+
// The cap is the shared panel's own hook, and it is ADDITIVE: the
|
|
101
|
+
// historic ceiling stays the fallback, so every other consumer of
|
|
102
|
+
// HkSelectPanel renders exactly as before.
|
|
103
|
+
const panelScss = readFileSync(join(here, "HkSelect.scss"), "utf-8");
|
|
104
|
+
// The hook rides the dvh-capable branch: on an engine without dvh the
|
|
105
|
+
// plain-vh ceiling above it stays the cap — a var()-substituted dvh
|
|
106
|
+
// value would be invalid at computed-value time and mean "no cap".
|
|
107
|
+
const popout = panelScss.slice(
|
|
108
|
+
panelScss.indexOf(".hk-select-popout {"),
|
|
109
|
+
panelScss.indexOf(".hk-select-option"),
|
|
110
|
+
);
|
|
111
|
+
// …and the block's POSITION is load-bearing: the hook must come after
|
|
112
|
+
// both literal ceilings, or the dvh-capable branch would override them
|
|
113
|
+
// with its own fallback and the consumer cap would never apply.
|
|
114
|
+
expect(popout).toContain("@supports (height: 1dvh)");
|
|
115
|
+
expect(popout.indexOf("@supports")).toBeGreaterThan(
|
|
116
|
+
popout.indexOf("max-height: max(240px, min(36rem, calc(100vh - 32px)));"),
|
|
117
|
+
);
|
|
118
|
+
expect(popout.indexOf("@supports")).toBeGreaterThan(
|
|
119
|
+
popout.lastIndexOf("max-height: max(240px, min(36rem, calc(100dvh - 32px)));"),
|
|
120
|
+
);
|
|
121
|
+
expect(popout).toMatch(
|
|
122
|
+
/max-height:\s*var\(\s*--hk-select-panel-max-height,\s*max\(240px, min\(36rem, calc\(100dvh - 32px\)\)\)\s*\)/,
|
|
123
|
+
);
|
|
124
|
+
expect(popout).toContain("max-height: max(240px, min(36rem, calc(100vh - 32px)));");
|
|
125
|
+
// The sheet band the same prop caps falls back to being uncapped — the
|
|
126
|
+
// property's own initial value, i.e. today's behaviour.
|
|
127
|
+
expect(panelScss).toMatch(
|
|
128
|
+
/max-height:\s*var\(--hk-select-panel-max-height,\s*none\)/,
|
|
129
|
+
);
|
|
130
|
+
const panelTsx = readFileSync(join(here, "HkSelectPanel.tsx"), "utf-8");
|
|
131
|
+
expect(panelTsx).toContain("maxHeight: { type: String, default: undefined }");
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
it("animates both lists through the shared list transition (FLIP move)", () => {
|
|
135
|
+
const groups = [...tsx.matchAll(/<HkListTransition\b[^>]*>/g)].map((m) => m[0]);
|
|
136
|
+
expect(groups, "the chips and the rows are both transition groups").toHaveLength(2);
|
|
137
|
+
for (const group of groups) {
|
|
138
|
+
expect(group).toContain('variant="reveal"');
|
|
139
|
+
expect(group).toMatch(/\bmove\b/);
|
|
140
|
+
// A fragment tag: the group adds no wrapper element, so the chips
|
|
141
|
+
// stay the field's own flex items and the rows stay the listbox's
|
|
142
|
+
// own children.
|
|
143
|
+
expect(group).toContain('tag=""');
|
|
144
|
+
}
|
|
72
145
|
});
|
|
73
146
|
});
|