@fusion.dev/helix-core 0.159.0 → 0.161.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/custom-elements.json +449 -20
- package/dist/controllers/virtualize-controller/virtualize-controller.d.ts +22 -0
- package/dist/controllers/virtualize-controller/virtualize-controller.js +22 -5
- package/dist/events/details.d.ts +6 -0
- package/dist/index.d.ts +3 -3
- package/dist/index.js +1 -1
- package/dist/mixins/mixin-controller-active/mixin-controller-active.d.ts +6 -2
- package/dist/mixins/mixin-controller-active/mixin-controller-active.js +2 -2
- package/dist/mixins/mixin-field/mixin-field.js +2 -1
- package/dist/mixins/mixin-picker-feedback/mixin-picker-feedback.d.ts +13 -1
- package/dist/mixins/mixin-picker-feedback/mixin-picker-feedback.js +26 -3
- package/dist/mixins/mixin-segmented-field/mixin-segmented-field.d.ts +12 -0
- package/dist/mixins/mixin-segmented-field/mixin-segmented-field.js +96 -39
- package/dist/utils/calendar/calendar.d.ts +50 -22
- package/dist/utils/calendar/calendar.js +134 -32
- package/dist/utils/date-segments/date-segments.d.ts +46 -14
- package/dist/utils/date-segments/date-segments.js +175 -27
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +4 -4
|
@@ -7,6 +7,12 @@ export interface VirtualizeOptions {
|
|
|
7
7
|
count: () => number;
|
|
8
8
|
/** How tall one item is along the scroll axis, in pixels. */
|
|
9
9
|
estimate: () => number;
|
|
10
|
+
/**
|
|
11
|
+
* The space between two items along the scroll axis, in pixels, which the
|
|
12
|
+
* host draws, such as a flex or grid `gap`. Each item counts it as part of
|
|
13
|
+
* its own size. Zero by default.
|
|
14
|
+
*/
|
|
15
|
+
gap?: () => number;
|
|
10
16
|
/**
|
|
11
17
|
* Rows kept either side of what fits, so a scroll reveals something drawn
|
|
12
18
|
* rather than something blank. Four by default.
|
|
@@ -213,6 +219,20 @@ export declare class VirtualizeController implements ReactiveController {
|
|
|
213
219
|
* last move is out of date.
|
|
214
220
|
*/
|
|
215
221
|
scrolling(): boolean;
|
|
222
|
+
/**
|
|
223
|
+
* Takes the offset as the box's own where the box moved it up only because
|
|
224
|
+
* the set below it got shorter, which the browser does to keep the offset
|
|
225
|
+
* inside the content, and answers where it was before.
|
|
226
|
+
*
|
|
227
|
+
* **Not a reader scrolling.** Rows at the end of the set measured shorter
|
|
228
|
+
* than their estimates shrink the content, and the box pulls its offset back
|
|
229
|
+
* to the new bottom before any `scroll` event arrives. Read as a scroll, the
|
|
230
|
+
* difference went into the spacer as drift and was then paid a second time:
|
|
231
|
+
* in Chromium, End on 2,000 rows of one to five lines left the window 292
|
|
232
|
+
* pixels above the last item, which stayed drawn below the box as the row
|
|
233
|
+
* holding focus.
|
|
234
|
+
*/
|
|
235
|
+
private noteClamp;
|
|
216
236
|
/** Squares the spacer and the scroll offset once the box is still. */
|
|
217
237
|
private readonly rest;
|
|
218
238
|
/**
|
|
@@ -264,6 +284,8 @@ export declare class VirtualizeController implements ReactiveController {
|
|
|
264
284
|
* Reading the drawn rows once corrects the slope, and it is still a line.
|
|
265
285
|
*/
|
|
266
286
|
private line;
|
|
287
|
+
/** The space the host draws between two items, which each item counts. */
|
|
288
|
+
private gap;
|
|
267
289
|
/**
|
|
268
290
|
* Reads the drawn rows' height once for the straight line, for a host that
|
|
269
291
|
* offers `rows` and does not measure.
|
|
@@ -17,7 +17,11 @@ export class VirtualizeController {
|
|
|
17
17
|
this.touching = false;
|
|
18
18
|
this.uneven = false;
|
|
19
19
|
this.handleScroll = () => {
|
|
20
|
-
const
|
|
20
|
+
const box = this.options.container();
|
|
21
|
+
if (box) {
|
|
22
|
+
this.noteClamp(box);
|
|
23
|
+
}
|
|
24
|
+
const top = box?.scrollTop ?? 0;
|
|
21
25
|
if (top !== this.lastTop) {
|
|
22
26
|
this.heading = top > this.lastTop ? 1 : -1;
|
|
23
27
|
this.lastTop = top;
|
|
@@ -95,6 +99,15 @@ export class VirtualizeController {
|
|
|
95
99
|
this.resting !== undefined ||
|
|
96
100
|
(this.options.container()?.scrollTop ?? this.lastTop) !== this.lastTop);
|
|
97
101
|
}
|
|
102
|
+
noteClamp(box) {
|
|
103
|
+
const was = this.lastTop;
|
|
104
|
+
if (!this.touching &&
|
|
105
|
+
box.scrollTop < was &&
|
|
106
|
+
box.scrollTop >= box.scrollHeight - box.clientHeight - 1) {
|
|
107
|
+
this.lastTop = box.scrollTop;
|
|
108
|
+
}
|
|
109
|
+
return was;
|
|
110
|
+
}
|
|
98
111
|
square() {
|
|
99
112
|
const box = this.options.container();
|
|
100
113
|
if (this.drift > 0 && box) {
|
|
@@ -161,7 +174,10 @@ export class VirtualizeController {
|
|
|
161
174
|
}
|
|
162
175
|
line() {
|
|
163
176
|
const estimate = Math.max(1, this.options.estimate());
|
|
164
|
-
return this.sample?.estimate === estimate ? this.sample.size : estimate;
|
|
177
|
+
return this.sample?.estimate === estimate ? this.sample.size : estimate + this.gap();
|
|
178
|
+
}
|
|
179
|
+
gap() {
|
|
180
|
+
return Math.max(0, this.options.gap?.() ?? 0);
|
|
165
181
|
}
|
|
166
182
|
sampleRows() {
|
|
167
183
|
const estimate = Math.max(1, this.options.estimate());
|
|
@@ -176,7 +192,7 @@ export class VirtualizeController {
|
|
|
176
192
|
const uneven = Math.max(...seen) - Math.min(...seen) > 1;
|
|
177
193
|
if (this.sample?.estimate !== estimate) {
|
|
178
194
|
const was = this.line();
|
|
179
|
-
const size = Math.max(1, seen.reduce((sum, height) => sum + height, 0) / seen.length);
|
|
195
|
+
const size = Math.max(1, seen.reduce((sum, height) => sum + height, 0) / seen.length) + this.gap();
|
|
180
196
|
this.sample = { estimate, size };
|
|
181
197
|
if (Math.abs(size - was) >= 0.5) {
|
|
182
198
|
const box = this.options.container();
|
|
@@ -291,12 +307,13 @@ export class VirtualizeController {
|
|
|
291
307
|
}
|
|
292
308
|
const total = Math.max(0, this.options.count());
|
|
293
309
|
const box = this.options.container();
|
|
310
|
+
const from = box ? this.noteClamp(box) : this.lastTop;
|
|
294
311
|
const top = box ? this.offset(box) : 0;
|
|
295
312
|
const anchor = this.target ?? this.heights.indexAt(top, total);
|
|
296
313
|
const was = this.heights.before(anchor, total);
|
|
297
314
|
let news = false;
|
|
298
315
|
for (const { element, index } of rows) {
|
|
299
|
-
if (this.heights.record(index, element.getBoundingClientRect().height)) {
|
|
316
|
+
if (this.heights.record(index, element.getBoundingClientRect().height + this.gap())) {
|
|
300
317
|
news = true;
|
|
301
318
|
}
|
|
302
319
|
}
|
|
@@ -310,7 +327,7 @@ export class VirtualizeController {
|
|
|
310
327
|
this.drift -= moved;
|
|
311
328
|
}
|
|
312
329
|
else {
|
|
313
|
-
box.scrollTop
|
|
330
|
+
box.scrollTop = from + moved;
|
|
314
331
|
this.lastTop = box.scrollTop;
|
|
315
332
|
}
|
|
316
333
|
}
|
package/dist/events/details.d.ts
CHANGED
|
@@ -258,6 +258,12 @@ export interface TourAskDetail {
|
|
|
258
258
|
* that `event.target` does not.
|
|
259
259
|
*/
|
|
260
260
|
export interface ChangeDetail {
|
|
261
|
+
/**
|
|
262
|
+
* Why the change was asked for, where a family names its causes. Video
|
|
263
|
+
* Playlist sends `ended`, `next`, `press`, `previous`, and `removed` on
|
|
264
|
+
* `helix-before-activate`.
|
|
265
|
+
*/
|
|
266
|
+
cause?: string;
|
|
261
267
|
/** The value after the change. */
|
|
262
268
|
value: string;
|
|
263
269
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -46,7 +46,7 @@ export { MixinControllerValue, type MixinControllerValueInterface, } from './mix
|
|
|
46
46
|
export { type DismissibleElement, MixinDismissible, } from './mixins/mixin-dismissible/mixin-dismissible.js';
|
|
47
47
|
export { MixinField, type MixinFieldInterface } from './mixins/mixin-field/mixin-field.js';
|
|
48
48
|
export { MixinPickerEditing, type MixinPickerEditingInterface, } from './mixins/mixin-picker-editing/mixin-picker-editing.js';
|
|
49
|
-
export { MixinPickerFeedback, type MixinPickerFeedbackInterface, } from './mixins/mixin-picker-feedback/mixin-picker-feedback.js';
|
|
49
|
+
export { MixinPickerFeedback, type MixinPickerFeedbackInterface, type PickerFailure, } from './mixins/mixin-picker-feedback/mixin-picker-feedback.js';
|
|
50
50
|
export { MixinPickerField, type MixinPickerFieldInterface, } from './mixins/mixin-picker-field/mixin-picker-field.js';
|
|
51
51
|
export { MixinSegmentedField, type MixinSegmentedFieldInterface, } from './mixins/mixin-segmented-field/mixin-segmented-field.js';
|
|
52
52
|
export { MixinSelectableItem, type MixinSelectableItemInterface, } from './mixins/mixin-selectable-item/mixin-selectable-item.js';
|
|
@@ -56,7 +56,7 @@ export { anchorElement } from './utils/anchor-element/anchor-element.js';
|
|
|
56
56
|
export { arrowReach, sideOf } from './utils/arrow/arrow.js';
|
|
57
57
|
export { assignSlots } from './utils/assign-slots/assign-slots.js';
|
|
58
58
|
export { type DrawAutofillOptions, drawAutofill, } from './utils/autofill-select/autofill-select.js';
|
|
59
|
-
export { addDays, addMonths, type Barring, barredBy, type CalendarView, calendarLocale, type DateCalendar, localToday, MONTH_CALENDARS, type MonthCalendar, type MonthSystem, monthOf, monthOut, monthSystem, type PeriodCell, type PeriodPage, periodCells, periodKey, periodMove, sameMonth, todayIn, weekendOf, weekStartOf, YEARS_SHOWN, yearOut, yearPageOf, } from './utils/calendar/calendar.js';
|
|
59
|
+
export { addDays, addMonths, type Barring, barredBy, type CalendarParts, type CalendarView, calendarLocale, type DateCalendar, localToday, MONTH_CALENDARS, type MonthCalendar, type MonthSystem, monthOf, monthOut, monthSystem, type PeriodCell, type PeriodPage, periodCells, periodKey, periodMove, sameMonth, todayIn, weekendOf, weekStartOf, YEARS_SHOWN, yearOut, yearPageOf, } from './utils/calendar/calendar.js';
|
|
60
60
|
export { capturePointer } from './utils/capture-pointer/capture-pointer.js';
|
|
61
61
|
export { centerInView } from './utils/center-in-view/center-in-view.js';
|
|
62
62
|
export { ariaChord, type ChordPress, chordParts, matchesChord, printableChord, selectsAll, } from './utils/chord/chord.js';
|
|
@@ -72,7 +72,7 @@ export { createSheet } from './utils/create-sheet/create-sheet.js';
|
|
|
72
72
|
export { type Color, type ColorFormat, formatColor, parseColor, toCss, toHsl, toOklch, toRgb, } from './utils/css-color/css-color.js';
|
|
73
73
|
export { customElement, definedTags, duplicateRegistrations, } from './utils/custom-element/custom-element.js';
|
|
74
74
|
export { type DataOption, type DataOptionItem, dataOptions, drawOptions, windowOf, } from './utils/data-options/data-options.js';
|
|
75
|
-
export { segmentName } from './utils/date-segments/date-segments.js';
|
|
75
|
+
export { readWrittenDate, readWrittenTime, segmentName, } from './utils/date-segments/date-segments.js';
|
|
76
76
|
export { type DrawDismissOptions, drawDismiss, drawDismissGlyph, } from './utils/draw-dismiss/draw-dismiss.js';
|
|
77
77
|
export { type DrawIconOptions, drawIcon } from './utils/draw-icon/draw-icon.js';
|
|
78
78
|
export { CHART_PLOT, drawTooltip, type ReadingCursor } from './utils/draw-tooltip/draw-tooltip.js';
|
package/dist/index.js
CHANGED
|
@@ -72,7 +72,7 @@ export { createSheet } from './utils/create-sheet/create-sheet.js';
|
|
|
72
72
|
export { formatColor, parseColor, toCss, toHsl, toOklch, toRgb, } from './utils/css-color/css-color.js';
|
|
73
73
|
export { customElement, definedTags, duplicateRegistrations, } from './utils/custom-element/custom-element.js';
|
|
74
74
|
export { dataOptions, drawOptions, windowOf, } from './utils/data-options/data-options.js';
|
|
75
|
-
export { segmentName } from './utils/date-segments/date-segments.js';
|
|
75
|
+
export { readWrittenDate, readWrittenTime, segmentName, } from './utils/date-segments/date-segments.js';
|
|
76
76
|
export { drawDismiss, drawDismissGlyph, } from './utils/draw-dismiss/draw-dismiss.js';
|
|
77
77
|
export { drawIcon } from './utils/draw-icon/draw-icon.js';
|
|
78
78
|
export { CHART_PLOT, drawTooltip } from './utils/draw-tooltip/draw-tooltip.js';
|
|
@@ -21,8 +21,12 @@ export declare class MixinControllerActiveInterface {
|
|
|
21
21
|
* is written.
|
|
22
22
|
*/
|
|
23
23
|
defaultActive: string;
|
|
24
|
-
/**
|
|
25
|
-
|
|
24
|
+
/**
|
|
25
|
+
* Requests a new current item, and returns whether the change was applied.
|
|
26
|
+
* `cause` says why, where the family names its causes, and reaches
|
|
27
|
+
* `helix-before-activate` as `detail.cause`.
|
|
28
|
+
*/
|
|
29
|
+
requestActive(next: string, cause?: string): boolean;
|
|
26
30
|
/**
|
|
27
31
|
* Which item is current, given what there is to choose from.
|
|
28
32
|
*
|
|
@@ -23,7 +23,7 @@ export const MixinControllerActive = (Base) => {
|
|
|
23
23
|
this.activeState = next;
|
|
24
24
|
this.requestUpdate('active', previous);
|
|
25
25
|
}
|
|
26
|
-
requestActive(next) {
|
|
26
|
+
requestActive(next, cause) {
|
|
27
27
|
if (next === this.active) {
|
|
28
28
|
return true;
|
|
29
29
|
}
|
|
@@ -34,7 +34,7 @@ export const MixinControllerActive = (Base) => {
|
|
|
34
34
|
this.activeState = value;
|
|
35
35
|
this.requestUpdate('active', previous);
|
|
36
36
|
},
|
|
37
|
-
event: (value) => new HelixBeforeActivateEvent({ value }),
|
|
37
|
+
event: (value) => new HelixBeforeActivateEvent(cause === undefined ? { value } : { cause, value }),
|
|
38
38
|
host: this,
|
|
39
39
|
next,
|
|
40
40
|
});
|
|
@@ -151,7 +151,8 @@ export const MixinField = (Base) => {
|
|
|
151
151
|
super.willUpdate(changed);
|
|
152
152
|
this.shownFeedback = this.resolvedFeedback;
|
|
153
153
|
this.toggleState('invalid', this.shownFeedback !== '');
|
|
154
|
-
this.internals.ariaInvalid =
|
|
154
|
+
this.internals.ariaInvalid =
|
|
155
|
+
this.shownFeedback !== '' ? 'true' : this.validates ? 'false' : null;
|
|
155
156
|
}
|
|
156
157
|
formResetCallback() {
|
|
157
158
|
;
|
|
@@ -19,11 +19,23 @@ export declare class MixinPickerFeedbackInterface {
|
|
|
19
19
|
* picker and says why.
|
|
20
20
|
*/
|
|
21
21
|
invalid: boolean;
|
|
22
|
-
/**
|
|
22
|
+
/**
|
|
23
|
+
* Whether a reader is told the answer is wrong: `invalid`, `feedback`, a
|
|
24
|
+
* held answer the picker refuses, or a missing answer once the reader has
|
|
25
|
+
* committed one or a form has refused the picker.
|
|
26
|
+
*/
|
|
23
27
|
protected get marked(): boolean;
|
|
28
|
+
/**
|
|
29
|
+
* What the picker's own validation finds: `missing` for a required picker
|
|
30
|
+
* with nothing chosen, `refused` for an answer it holds and refuses, or
|
|
31
|
+
* nothing. Read in `willUpdate`, so a picker answers it from its properties.
|
|
32
|
+
*/
|
|
33
|
+
protected get failure(): PickerFailure | undefined;
|
|
24
34
|
/** The `feedback` line, or nothing while there is none. */
|
|
25
35
|
protected renderPickerFeedback(): TemplateResult | typeof nothing;
|
|
26
36
|
}
|
|
37
|
+
/** What a picker's own validation finds, which `marked` reads. */
|
|
38
|
+
export type PickerFailure = 'missing' | 'refused';
|
|
27
39
|
/**
|
|
28
40
|
* `feedback` and `invalid` for a picker that stands on its own as a form
|
|
29
41
|
* control: Color Picker, Date Picker and Time Picker.
|
|
@@ -5,7 +5,7 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
|
|
|
5
5
|
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
6
|
};
|
|
7
7
|
import { html, nothing } from 'lit';
|
|
8
|
-
import { property } from 'lit/decorators.js';
|
|
8
|
+
import { property, state } from 'lit/decorators.js';
|
|
9
9
|
import { hostAria } from '../../utils/host-aria/host-aria.js';
|
|
10
10
|
export const MixinPickerFeedback = (Base) => {
|
|
11
11
|
class PickerFeedbackElement extends Base {
|
|
@@ -13,16 +13,36 @@ export const MixinPickerFeedback = (Base) => {
|
|
|
13
13
|
super(...arguments);
|
|
14
14
|
this.feedback = '';
|
|
15
15
|
this.invalid = false;
|
|
16
|
+
this.touched = false;
|
|
16
17
|
}
|
|
17
18
|
get marked() {
|
|
18
|
-
|
|
19
|
+
if (this.invalid || this.feedback !== '') {
|
|
20
|
+
return true;
|
|
21
|
+
}
|
|
22
|
+
const failure = this.failure;
|
|
23
|
+
return failure === 'refused' || (failure === 'missing' && this.touched);
|
|
24
|
+
}
|
|
25
|
+
get failure() {
|
|
26
|
+
return undefined;
|
|
27
|
+
}
|
|
28
|
+
formResetCallback() {
|
|
29
|
+
super.formResetCallback();
|
|
30
|
+
this.touched = false;
|
|
31
|
+
}
|
|
32
|
+
firstUpdated(changed) {
|
|
33
|
+
super.firstUpdated(changed);
|
|
34
|
+
const touch = () => {
|
|
35
|
+
this.touched = true;
|
|
36
|
+
};
|
|
37
|
+
this.addEventListener('invalid', touch);
|
|
38
|
+
this.addEventListener('change', touch);
|
|
19
39
|
}
|
|
20
40
|
get validityFeedback() {
|
|
21
41
|
return this.feedback;
|
|
22
42
|
}
|
|
23
43
|
willUpdate(changed) {
|
|
24
44
|
super.willUpdate(changed);
|
|
25
|
-
this.internals.ariaInvalid = this.marked ? 'true' :
|
|
45
|
+
this.internals.ariaInvalid = this.marked ? 'true' : 'false';
|
|
26
46
|
this.toggleState('invalid', this.marked);
|
|
27
47
|
}
|
|
28
48
|
updated(changed) {
|
|
@@ -42,5 +62,8 @@ export const MixinPickerFeedback = (Base) => {
|
|
|
42
62
|
__decorate([
|
|
43
63
|
property({ reflect: true, type: Boolean })
|
|
44
64
|
], PickerFeedbackElement.prototype, "invalid", void 0);
|
|
65
|
+
__decorate([
|
|
66
|
+
state()
|
|
67
|
+
], PickerFeedbackElement.prototype, "touched", void 0);
|
|
45
68
|
return PickerFeedbackElement;
|
|
46
69
|
};
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { type TemplateResult } from 'lit';
|
|
2
2
|
import type { HelixFormElement } from '../../helix-form-element/helix-form-element.js';
|
|
3
|
+
import type { MonthSystem } from '../../utils/calendar/calendar.js';
|
|
3
4
|
import { type SegmentedKind, type SegmentOptions } from '../../utils/date-segments/date-segments.js';
|
|
4
5
|
import type { MixinControllerOpenInterface } from '../mixin-controller-open/mixin-controller-open.js';
|
|
5
6
|
import type { MixinControllerValueInterface } from '../mixin-controller-value/mixin-controller-value.js';
|
|
@@ -34,8 +35,17 @@ export declare class MixinSegmentedFieldInterface {
|
|
|
34
35
|
* time, in place of a line of text.
|
|
35
36
|
*/
|
|
36
37
|
segmented: boolean;
|
|
38
|
+
/**
|
|
39
|
+
* What a screen reader hears before the segments of a range's first date or
|
|
40
|
+
* time, after each segment's own name.
|
|
41
|
+
*/
|
|
42
|
+
rangeStartLabel: string;
|
|
43
|
+
/** What a screen reader hears before the segments of a range's second date or time. */
|
|
44
|
+
rangeEndLabel: string;
|
|
37
45
|
/** Whether the segments are drawn: `segmented`, where the family can hold them. */
|
|
38
46
|
protected get segmentsShown(): boolean;
|
|
47
|
+
/** How many rows of segments the value takes: two for a range, one otherwise. */
|
|
48
|
+
protected get segmentGroups(): number;
|
|
39
49
|
/** The row of segments, the group a description is carried to, or `null` while none is drawn. */
|
|
40
50
|
protected get segmentGroup(): HTMLElement | null;
|
|
41
51
|
/** Whether this family can show segments in its current shape. */
|
|
@@ -46,6 +56,8 @@ export declare class MixinSegmentedFieldInterface {
|
|
|
46
56
|
protected get segmentLocale(): string;
|
|
47
57
|
/** The clock and the seconds a time is laid out with. */
|
|
48
58
|
protected get segmentOptions(): SegmentOptions;
|
|
59
|
+
/** The calendar a date's parts are counted in, or `undefined` for the Gregorian calendar. */
|
|
60
|
+
protected get segmentSystem(): MonthSystem | undefined;
|
|
49
61
|
/** The row of segments, in the locale's order, with what it writes between them. */
|
|
50
62
|
protected renderSegments(): TemplateResult;
|
|
51
63
|
}
|
|
@@ -18,18 +18,23 @@ export const MixinSegmentedField = (Base) => {
|
|
|
18
18
|
this.emptyMessage = '{kind, select, date {Enter a date.} other {Enter a time.}}';
|
|
19
19
|
this.emptySegmentLabel = 'Empty';
|
|
20
20
|
this.incompleteMessage = '{kind, select, date {Enter a whole date.} other {Enter a whole time.}}';
|
|
21
|
+
this.rangeEndLabel = 'End';
|
|
22
|
+
this.rangeStartLabel = 'Start';
|
|
21
23
|
this.segmented = false;
|
|
22
|
-
this.parts = {};
|
|
24
|
+
this.parts = [{}];
|
|
23
25
|
this.typed = '';
|
|
24
26
|
this.own = false;
|
|
25
27
|
this.touchApple = appleTouch(typeof navigator === 'undefined' ? undefined : navigator);
|
|
26
28
|
this.entered = undefined;
|
|
29
|
+
this.opener = undefined;
|
|
27
30
|
this.handleSegmentKeydown = (event) => {
|
|
28
31
|
const element = event.currentTarget;
|
|
29
32
|
const kind = element.dataset.segment;
|
|
33
|
+
const row = Number(element.dataset.row ?? 0);
|
|
30
34
|
const twelve = isTwelveHourLayout(this.layout);
|
|
31
35
|
if (event.key === 'ArrowDown' && event.altKey) {
|
|
32
36
|
event.preventDefault();
|
|
37
|
+
this.opener = element;
|
|
33
38
|
this.handleTriggerClick();
|
|
34
39
|
return;
|
|
35
40
|
}
|
|
@@ -57,14 +62,15 @@ export const MixinSegmentedField = (Base) => {
|
|
|
57
62
|
const edit = segmentKey(event.key, {
|
|
58
63
|
kind,
|
|
59
64
|
locale: this.segmentLocale,
|
|
60
|
-
segments: this.parts,
|
|
65
|
+
segments: this.parts[row] ?? {},
|
|
66
|
+
system: this.segmentSystem,
|
|
61
67
|
twelve,
|
|
62
68
|
typed: this.typed,
|
|
63
69
|
});
|
|
64
70
|
if (edit) {
|
|
65
71
|
event.preventDefault();
|
|
66
72
|
this.typed = edit.typed;
|
|
67
|
-
this.take(edit.segments);
|
|
73
|
+
this.take(row, edit.segments);
|
|
68
74
|
if (edit.advance) {
|
|
69
75
|
this.moveFocus(element, 1);
|
|
70
76
|
}
|
|
@@ -96,12 +102,34 @@ export const MixinSegmentedField = (Base) => {
|
|
|
96
102
|
};
|
|
97
103
|
}
|
|
98
104
|
static { this.words = ''; }
|
|
105
|
+
returnFocus() {
|
|
106
|
+
const opener = this.opener;
|
|
107
|
+
this.opener = undefined;
|
|
108
|
+
if (opener?.isConnected && this.segmentsShown) {
|
|
109
|
+
this.returning = true;
|
|
110
|
+
opener.focus();
|
|
111
|
+
this.returning = false;
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
super.returnFocus();
|
|
115
|
+
}
|
|
99
116
|
get segmentable() {
|
|
100
117
|
return true;
|
|
101
118
|
}
|
|
102
119
|
get segmentsShown() {
|
|
103
120
|
return this.segmented && this.segmentable;
|
|
104
121
|
}
|
|
122
|
+
get segmentGroups() {
|
|
123
|
+
return 1;
|
|
124
|
+
}
|
|
125
|
+
joined(rows) {
|
|
126
|
+
const values = rows.map((one) => segmentsValue(this.segmentedKind, one, this.layout, this.segmentSystem));
|
|
127
|
+
return values.some((one) => one === '') ? '' : values.join(',');
|
|
128
|
+
}
|
|
129
|
+
rowsOf(value) {
|
|
130
|
+
const values = this.segmentGroups > 1 ? value.split(',') : [value];
|
|
131
|
+
return Array.from({ length: this.segmentGroups }, (_, row) => segmentsOf(this.segmentedKind, (values[row] ?? '').trim(), this.segmentSystem));
|
|
132
|
+
}
|
|
105
133
|
get segmentedKind() {
|
|
106
134
|
return 'date';
|
|
107
135
|
}
|
|
@@ -111,6 +139,9 @@ export const MixinSegmentedField = (Base) => {
|
|
|
111
139
|
get segmentOptions() {
|
|
112
140
|
return {};
|
|
113
141
|
}
|
|
142
|
+
get segmentSystem() {
|
|
143
|
+
return undefined;
|
|
144
|
+
}
|
|
114
145
|
get layout() {
|
|
115
146
|
return segmentLayout(this.segmentedKind, this.segmentLocale, this.segmentOptions);
|
|
116
147
|
}
|
|
@@ -126,9 +157,9 @@ export const MixinSegmentedField = (Base) => {
|
|
|
126
157
|
get segmentElements() {
|
|
127
158
|
return [...(this.renderRoot?.querySelectorAll('[part~="segment"]') ?? [])];
|
|
128
159
|
}
|
|
129
|
-
take(next) {
|
|
130
|
-
this.parts = next;
|
|
131
|
-
const value =
|
|
160
|
+
take(row, next) {
|
|
161
|
+
this.parts = this.parts.map((one, index) => (index === row ? next : one));
|
|
162
|
+
const value = this.joined(this.parts);
|
|
132
163
|
if (value === this.value) {
|
|
133
164
|
this.sync();
|
|
134
165
|
return;
|
|
@@ -136,7 +167,7 @@ export const MixinSegmentedField = (Base) => {
|
|
|
136
167
|
this.own = true;
|
|
137
168
|
if (!this.requestValue(value)) {
|
|
138
169
|
this.own = false;
|
|
139
|
-
this.parts =
|
|
170
|
+
this.parts = this.rowsOf(this.value);
|
|
140
171
|
}
|
|
141
172
|
}
|
|
142
173
|
moveFocus(from, by) {
|
|
@@ -149,13 +180,13 @@ export const MixinSegmentedField = (Base) => {
|
|
|
149
180
|
this.own = false;
|
|
150
181
|
if (!own &&
|
|
151
182
|
this.segmentsShown &&
|
|
152
|
-
|
|
153
|
-
this.parts =
|
|
183
|
+
(this.parts.length !== this.segmentGroups || this.joined(this.parts) !== this.value)) {
|
|
184
|
+
this.parts = this.rowsOf(this.value);
|
|
154
185
|
}
|
|
155
186
|
}
|
|
156
187
|
formResetCallback() {
|
|
157
188
|
super.formResetCallback();
|
|
158
|
-
this.parts =
|
|
189
|
+
this.parts = this.rowsOf('');
|
|
159
190
|
this.typed = '';
|
|
160
191
|
}
|
|
161
192
|
connectedCallback() {
|
|
@@ -175,7 +206,9 @@ export const MixinSegmentedField = (Base) => {
|
|
|
175
206
|
return;
|
|
176
207
|
}
|
|
177
208
|
const anchor = this.anchor ?? undefined;
|
|
178
|
-
const partial = isPartial(this.segmentedKind, this.
|
|
209
|
+
const partial = this.parts.some((one) => isPartial(this.segmentedKind, one, this.layout, this.segmentSystem)) ||
|
|
210
|
+
(this.value === '' &&
|
|
211
|
+
this.parts.some((one) => Object.values(one).some((part) => part !== undefined)));
|
|
179
212
|
const fault = verdictFlags(this.validate());
|
|
180
213
|
if (partial) {
|
|
181
214
|
this.internals.setValidity({ badInput: true }, this.badInputMessage ||
|
|
@@ -197,51 +230,67 @@ export const MixinSegmentedField = (Base) => {
|
|
|
197
230
|
const layout = this.layout;
|
|
198
231
|
const twelve = isTwelveHourLayout(layout);
|
|
199
232
|
const locale = this.segmentLocale;
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
233
|
+
const system = this.segmentSystem;
|
|
234
|
+
const rows = this.segmentGroups;
|
|
235
|
+
const row = (index) => {
|
|
236
|
+
const parts = this.parts[index] ?? {};
|
|
237
|
+
const prefix = index === 0 ? this.controlId : `${this.controlId}-end`;
|
|
238
|
+
const rowName = rows > 1 ? `${prefix}-row` : '';
|
|
239
|
+
return html `${rows > 1
|
|
240
|
+
? html `<span id=${rowName} hidden
|
|
241
|
+
>${index === 0 ? this.rangeStartLabel : this.rangeEndLabel}</span
|
|
242
|
+
>`
|
|
243
|
+
: nothing}${layout.map((slot) => {
|
|
244
|
+
if ('literal' in slot) {
|
|
245
|
+
return html `<span part="literal" aria-hidden="true">${slot.literal}</span>`;
|
|
246
|
+
}
|
|
247
|
+
const kind = slot.kind;
|
|
248
|
+
const shown = shownValue(kind, parts, twelve);
|
|
249
|
+
const { max, min } = segmentRange(kind, parts, twelve, system);
|
|
250
|
+
const text = segmentText(kind, parts, twelve, locale);
|
|
251
|
+
const empty = shown === undefined;
|
|
252
|
+
const id = `${prefix}-${kind}`;
|
|
253
|
+
const editable = !this.isDisabled && !this.readOnly;
|
|
254
|
+
const spin = !this.touchApple;
|
|
255
|
+
const named = [id, rowName, this.labeled ? this.labelId : ''].filter(Boolean).join(' ');
|
|
256
|
+
return html `<span
|
|
224
257
|
part="segment${empty ? ' segment-placeholder' : ''}"
|
|
225
258
|
id=${id}
|
|
259
|
+
data-row=${rows > 1 ? index : nothing}
|
|
226
260
|
data-segment=${kind}
|
|
227
261
|
role=${spin ? 'spinbutton' : 'textbox'}
|
|
228
262
|
tabindex=${this.isDisabled ? '-1' : '0'}
|
|
229
263
|
inputmode=${kind === 'dayPeriod' ? nothing : 'numeric'}
|
|
230
264
|
enterkeyhint=${editable ? 'next' : nothing}
|
|
231
|
-
aria-label=${
|
|
232
|
-
aria-labelledby=${
|
|
265
|
+
aria-label=${named === id ? segmentName(kind, locale) : `${segmentName(kind, locale)},`}
|
|
266
|
+
aria-labelledby=${named === id ? nothing : named}
|
|
233
267
|
aria-invalid=${this.invalidity ?? nothing}
|
|
234
268
|
aria-valuemin=${spin ? min : nothing}
|
|
235
269
|
aria-valuemax=${spin ? max : nothing}
|
|
236
270
|
aria-valuenow=${spin ? (shown ?? nothing) : nothing}
|
|
237
|
-
aria-valuetext=${spin ? (empty ? this.emptySegmentLabel : segmentValueText(kind,
|
|
271
|
+
aria-valuetext=${spin ? (empty ? this.emptySegmentLabel : segmentValueText(kind, parts, twelve, locale, system)) : nothing}
|
|
238
272
|
aria-disabled=${this.isDisabled ? 'true' : nothing}
|
|
239
273
|
aria-required=${this.required ? 'true' : nothing}
|
|
240
274
|
aria-readonly=${this.readOnly ? 'true' : nothing}
|
|
241
275
|
@keydown=${this.handleSegmentKeydown}
|
|
242
276
|
>${text}</span
|
|
243
277
|
>`;
|
|
244
|
-
|
|
278
|
+
})}`;
|
|
279
|
+
};
|
|
280
|
+
return html `<div
|
|
281
|
+
part="segments"
|
|
282
|
+
id=${this.controlId}
|
|
283
|
+
role="group"
|
|
284
|
+
aria-labelledby=${this.labeled ? this.labelId : nothing}
|
|
285
|
+
aria-describedby=${this.describedBy ?? nothing}
|
|
286
|
+
aria-invalid=${this.invalidity ?? nothing}
|
|
287
|
+
@focusin=${this.handleSegmentsFocusIn}
|
|
288
|
+
@focusout=${this.handleSegmentsFocusOut}
|
|
289
|
+
@keypress=${this.handleKeypress}
|
|
290
|
+
>
|
|
291
|
+
${Array.from({ length: rows }, (_, index) => index === 0
|
|
292
|
+
? row(0)
|
|
293
|
+
: html `<span part="literal range-separator" aria-hidden="true"> – </span>${row(index)}`)}
|
|
245
294
|
</div>`;
|
|
246
295
|
}
|
|
247
296
|
}
|
|
@@ -257,6 +306,14 @@ export const MixinSegmentedField = (Base) => {
|
|
|
257
306
|
words,
|
|
258
307
|
property({ attribute: 'incomplete-message' })
|
|
259
308
|
], SegmentedFieldElement.prototype, "incompleteMessage", void 0);
|
|
309
|
+
__decorate([
|
|
310
|
+
words,
|
|
311
|
+
property({ attribute: 'range-end-label' })
|
|
312
|
+
], SegmentedFieldElement.prototype, "rangeEndLabel", void 0);
|
|
313
|
+
__decorate([
|
|
314
|
+
words,
|
|
315
|
+
property({ attribute: 'range-start-label' })
|
|
316
|
+
], SegmentedFieldElement.prototype, "rangeStartLabel", void 0);
|
|
260
317
|
__decorate([
|
|
261
318
|
property({ type: Boolean })
|
|
262
319
|
], SegmentedFieldElement.prototype, "segmented", void 0);
|