@fusion.dev/helix-core 0.103.3 → 0.104.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.
Files changed (37) hide show
  1. package/custom-elements.json +1384 -150
  2. package/dist/controllers/arrival-controller/arrival-controller.d.ts +43 -0
  3. package/dist/controllers/arrival-controller/arrival-controller.js +35 -0
  4. package/dist/controllers/cue-controller/cue-controller.d.ts +8 -0
  5. package/dist/controllers/cue-controller/cue-controller.js +4 -1
  6. package/dist/controllers/keyboard-navigation-controller/keyboard-navigation-controller.js +4 -0
  7. package/dist/controllers/remote-playback-controller/remote-playback-controller.d.ts +15 -0
  8. package/dist/controllers/remote-playback-controller/remote-playback-controller.js +19 -3
  9. package/dist/events/events.d.ts +236 -0
  10. package/dist/events/events.js +144 -0
  11. package/dist/helix-form-element/helix-form-element.d.ts +9 -0
  12. package/dist/helix-form-element/helix-form-element.js +1 -0
  13. package/dist/index.d.ts +6 -1
  14. package/dist/index.js +6 -1
  15. package/dist/mixins/mixin-collection/mixin-collection.d.ts +6 -0
  16. package/dist/mixins/mixin-collection/mixin-collection.js +5 -1
  17. package/dist/mixins/mixin-controller-open/mixin-controller-open.js +3 -3
  18. package/dist/mixins/mixin-controller-value/mixin-controller-value.d.ts +1 -1
  19. package/dist/mixins/mixin-controller-value/mixin-controller-value.js +2 -1
  20. package/dist/mixins/mixin-field/mixin-field.d.ts +11 -0
  21. package/dist/mixins/mixin-field/mixin-field.js +25 -3
  22. package/dist/mixins/mixin-picker-field/mixin-picker-field.d.ts +8 -0
  23. package/dist/mixins/mixin-picker-field/mixin-picker-field.js +22 -2
  24. package/dist/mixins/mixin-tokens/mixin-tokens.js +0 -1
  25. package/dist/mixins/mixin-viewport/mixin-viewport.d.ts +17 -5
  26. package/dist/mixins/mixin-viewport/mixin-viewport.js +68 -21
  27. package/dist/utils/fill-template/fill-template.d.ts +13 -0
  28. package/dist/utils/fill-template/fill-template.js +1 -0
  29. package/dist/utils/key-claimed/key-claimed.d.ts +18 -0
  30. package/dist/utils/key-claimed/key-claimed.js +14 -0
  31. package/dist/utils/report-values/report-values.d.ts +21 -0
  32. package/dist/utils/report-values/report-values.js +11 -0
  33. package/dist/utils/slotted-words/slotted-words.d.ts +11 -0
  34. package/dist/utils/slotted-words/slotted-words.js +5 -0
  35. package/dist/utils/tell-disclosure/tell-disclosure.d.ts +3 -1
  36. package/dist/utils/tell-disclosure/tell-disclosure.js +1 -1
  37. package/package.json +4 -4
package/dist/index.d.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  export { AriaForwardController, type AriaForwardOptions, } from './controllers/aria-forward-controller/aria-forward-controller.js';
2
+ export { ArrivalController, type ArrivalOptions, } from './controllers/arrival-controller/arrival-controller.js';
2
3
  export { ChartPointerController, type ChartPointerHost, } from './controllers/chart-pointer-controller/chart-pointer-controller.js';
3
4
  export { CommandController, type CommandOptions, HelixFallbackCommandEvent, supportsInvokers, } from './controllers/command-controller/command-controller.js';
4
5
  export { CopyController, type CopyOptions, } from './controllers/copy-controller/copy-controller.js';
@@ -19,7 +20,7 @@ export { SourceController, type SourceHost, } from './controllers/source-control
19
20
  export { type TokenColor, TokenColorController, } from './controllers/token-color-controller/token-color-controller.js';
20
21
  export { type Rendered, VirtualizeController, type VirtualizeOptions, type Window as VirtualizeWindow, } from './controllers/virtualize-controller/virtualize-controller.js';
21
22
  export { eventsForTag, registerElementEvents } from './events/element-events.js';
22
- export { type CaretDetail, type ChangeDetail, type CompleteDetail, type ConsoleDetail, type CopyDetail, type CreateDetail, type DismissDetail, type FacetDetail, type FilterDetail, HelixActivateEvent, HelixBeforeActivateEvent, HelixBeforeChangeEvent, HelixBeforeCloseEvent, HelixBeforeCollapseEvent, HelixBeforeEditEvent, HelixBeforeOpenEvent, HelixBeforePanelResizeEvent, HelixBeforePinEvent, HelixBeforeResizeEvent, HelixBeforeSplitEvent, HelixBeforeToggleEvent, HelixCaretEvent, HelixCompleteEvent, HelixConsoleEvent, HelixCopyEvent, HelixCreateEvent, HelixDismissEvent, HelixFacetEvent, HelixFilterEvent, HelixHistoryEvent, HelixMapPanEvent, HelixMapZoomEvent, HelixNavigateEvent, HelixNextEvent, HelixPreviousEvent, HelixReadEvent, HelixReorderEvent, HelixResizeEvent, HelixSampleEndEvent, HelixSampleStartEvent, HelixScrolledEvent, HelixSelectEvent, HelixSendEvent, HelixShortcutEvent, HelixSortEvent, HelixTourAskEvent, type MapPanDetail, type MapZoomDetail, type NavigateDetail, type OpenDetail, type ReadDetail, type ResizeDetail, type ScrolledDetail, type SelectDetail, type SendDetail, type ShortcutDetail, type SkipDetail, type SortDetail, type ToggleDetail, type ViewportPanDetail, type ViewportZoomDetail, } from './events/events.js';
23
+ export { type CanvasView, type CaretDetail, type ChangeDetail, type CompleteDetail, type ConnectDetail, type ConsoleDetail, type CopyDetail, type CreateDetail, type DismissDetail, type FacetDetail, type FilterDetail, HelixActivateEvent, HelixBeforeActivateEvent, HelixBeforeChangeEvent, HelixBeforeCloseEvent, HelixBeforeCollapseEvent, HelixBeforeEditEvent, HelixBeforeMoveEvent, HelixBeforeOpenEvent, HelixBeforePanelResizeEvent, HelixBeforePinEvent, HelixBeforeReconnectEvent, HelixBeforeRelabelEvent, HelixBeforeResizeEvent, HelixBeforeSplitEvent, HelixBeforeToggleEvent, HelixCanvasEditEvent, HelixCanvasPanEvent, HelixCanvasViewEvent, HelixCanvasZoomEvent, HelixCaretEvent, HelixCompleteEvent, HelixConnectEvent, HelixConsoleEvent, HelixCopyEvent, HelixCreateEvent, HelixDismissEvent, HelixFacetEvent, HelixFilterEvent, HelixHistoryEvent, HelixMapPanEvent, HelixMapZoomEvent, HelixMoveEvent, HelixNavigateEvent, HelixNextEvent, HelixPreviousEvent, HelixReadEvent, HelixReconnectEvent, HelixRelabelEvent, HelixRemoveEvent, HelixReorderEvent, HelixResizeEvent, HelixSampleEndEvent, HelixSampleStartEvent, HelixScrolledEvent, HelixSelectEvent, HelixSendEvent, HelixShortcutEvent, HelixSortEvent, HelixTourAskEvent, type MapPanDetail, type MapZoomDetail, type MoveDetail, type NavigateDetail, type OpenDetail, type ReadDetail, type ReconnectDetail, type RelabelDetail, type RemoveDetail, type ResizeDetail, type ScrolledDetail, type SelectDetail, type SendDetail, type ShortcutDetail, type SkipDetail, type SortDetail, type ToggleDetail, type ViewportPanDetail, type ViewportZoomDetail, } from './events/events.js';
23
24
  export { HelixElement, whenHydrated } from './helix-element/helix-element.js';
24
25
  export { HelixFormElement } from './helix-form-element/helix-form-element.js';
25
26
  export { MixinChartSource, type MixinChartSourceInterface, PALETTE, PLOT_HEIGHT, PLOT_WIDTH, seriesIndex, seriesNames, } from './mixins/mixin-chart-source/mixin-chart-source.js';
@@ -45,12 +46,14 @@ export { type DrawIconOptions, drawIcon } from './utils/draw-icon/draw-icon.js';
45
46
  export { CHART_PLOT, drawTooltip } from './utils/draw-tooltip/draw-tooltip.js';
46
47
  export { elapsedTime } from './utils/elapsed-time/elapsed-time.js';
47
48
  export { type ElementByIdOptions, elementById, searchableById, } from './utils/element-by-id/element-by-id.js';
49
+ export { fillTemplate } from './utils/fill-template/fill-template.js';
48
50
  export { deepActiveElement, deepContains, focusableElements, } from './utils/focusable-elements/focusable-elements.js';
49
51
  export { formatNumbers } from './utils/format-numbers/format-numbers.js';
50
52
  export { type HostAria, hostAria } from './utils/host-aria/host-aria.js';
51
53
  export { type DisableableItem, forgetDisabled, inheritDisabled, } from './utils/inherit-disabled/inherit-disabled.js';
52
54
  export { inlineStep } from './utils/inline-step/inline-step.js';
53
55
  export { isRtl } from './utils/is-rtl/is-rtl.js';
56
+ export { keyClaimed } from './utils/key-claimed/key-claimed.js';
54
57
  export { type Curve, linePath, type Point } from './utils/line-path/line-path.js';
55
58
  export { markRegion, type RegionOptions } from './utils/mark-region/mark-region.js';
56
59
  export { copiesOf, type MediaCopy, sameCopies, } from './utils/media-copy/media-copy.js';
@@ -58,6 +61,7 @@ export { onUnlessFalse } from './utils/on-unless-false/on-unless-false.js';
58
61
  export { type ColorBytes, readColor } from './utils/read-color/read-color.js';
59
62
  export { type Row, type RowCell, readRows, readSeries, type Series, type SeriesPoint, } from './utils/read-series/read-series.js';
60
63
  export { type Relative, relativeTime, } from './utils/relative-time/relative-time.js';
64
+ export { reportValues } from './utils/report-values/report-values.js';
61
65
  export { type ChangeRequestOptions, requestChange, } from './utils/request-change/request-change.js';
62
66
  export { type ActivatableItem, resolveActiveItem, } from './utils/resolve-active-item/resolve-active-item.js';
63
67
  export { holdCaret, restoreValue } from './utils/restore-value/restore-value.js';
@@ -65,6 +69,7 @@ export { extent, linear, type Scale } from './utils/scale/scale.js';
65
69
  export { ticks } from './utils/scale/ticks.js';
66
70
  export { type ScrollItemOptions, scrollItemIntoView, } from './utils/scroll-item-into-view/scroll-item-into-view.js';
67
71
  export { slotFilled } from './utils/slot-filled/slot-filled.js';
72
+ export { slottedWords } from './utils/slotted-words/slotted-words.js';
68
73
  export { splitValues } from './utils/split-values/split-values.js';
69
74
  export { tellDisclosure } from './utils/tell-disclosure/tell-disclosure.js';
70
75
  export { subscribe } from './utils/ticker/ticker.js';
package/dist/index.js CHANGED
@@ -1,4 +1,5 @@
1
1
  export { AriaForwardController, } from './controllers/aria-forward-controller/aria-forward-controller.js';
2
+ export { ArrivalController, } from './controllers/arrival-controller/arrival-controller.js';
2
3
  export { ChartPointerController, } from './controllers/chart-pointer-controller/chart-pointer-controller.js';
3
4
  export { CommandController, HelixFallbackCommandEvent, supportsInvokers, } from './controllers/command-controller/command-controller.js';
4
5
  export { CopyController, } from './controllers/copy-controller/copy-controller.js';
@@ -19,7 +20,7 @@ export { SourceController, } from './controllers/source-controller/source-contro
19
20
  export { TokenColorController, } from './controllers/token-color-controller/token-color-controller.js';
20
21
  export { VirtualizeController, } from './controllers/virtualize-controller/virtualize-controller.js';
21
22
  export { eventsForTag, registerElementEvents } from './events/element-events.js';
22
- export { HelixActivateEvent, HelixBeforeActivateEvent, HelixBeforeChangeEvent, HelixBeforeCloseEvent, HelixBeforeCollapseEvent, HelixBeforeEditEvent, HelixBeforeOpenEvent, HelixBeforePanelResizeEvent, HelixBeforePinEvent, HelixBeforeResizeEvent, HelixBeforeSplitEvent, HelixBeforeToggleEvent, HelixCaretEvent, HelixCompleteEvent, HelixConsoleEvent, HelixCopyEvent, HelixCreateEvent, HelixDismissEvent, HelixFacetEvent, HelixFilterEvent, HelixHistoryEvent, HelixMapPanEvent, HelixMapZoomEvent, HelixNavigateEvent, HelixNextEvent, HelixPreviousEvent, HelixReadEvent, HelixReorderEvent, HelixResizeEvent, HelixSampleEndEvent, HelixSampleStartEvent, HelixScrolledEvent, HelixSelectEvent, HelixSendEvent, HelixShortcutEvent, HelixSortEvent, HelixTourAskEvent, } from './events/events.js';
23
+ export { HelixActivateEvent, HelixBeforeActivateEvent, HelixBeforeChangeEvent, HelixBeforeCloseEvent, HelixBeforeCollapseEvent, HelixBeforeEditEvent, HelixBeforeMoveEvent, HelixBeforeOpenEvent, HelixBeforePanelResizeEvent, HelixBeforePinEvent, HelixBeforeReconnectEvent, HelixBeforeRelabelEvent, HelixBeforeResizeEvent, HelixBeforeSplitEvent, HelixBeforeToggleEvent, HelixCanvasEditEvent, HelixCanvasPanEvent, HelixCanvasViewEvent, HelixCanvasZoomEvent, HelixCaretEvent, HelixCompleteEvent, HelixConnectEvent, HelixConsoleEvent, HelixCopyEvent, HelixCreateEvent, HelixDismissEvent, HelixFacetEvent, HelixFilterEvent, HelixHistoryEvent, HelixMapPanEvent, HelixMapZoomEvent, HelixMoveEvent, HelixNavigateEvent, HelixNextEvent, HelixPreviousEvent, HelixReadEvent, HelixReconnectEvent, HelixRelabelEvent, HelixRemoveEvent, HelixReorderEvent, HelixResizeEvent, HelixSampleEndEvent, HelixSampleStartEvent, HelixScrolledEvent, HelixSelectEvent, HelixSendEvent, HelixShortcutEvent, HelixSortEvent, HelixTourAskEvent, } from './events/events.js';
23
24
  export { HelixElement, whenHydrated } from './helix-element/helix-element.js';
24
25
  export { HelixFormElement } from './helix-form-element/helix-form-element.js';
25
26
  export { MixinChartSource, PALETTE, PLOT_HEIGHT, PLOT_WIDTH, seriesIndex, seriesNames, } from './mixins/mixin-chart-source/mixin-chart-source.js';
@@ -45,12 +46,14 @@ export { drawIcon } from './utils/draw-icon/draw-icon.js';
45
46
  export { CHART_PLOT, drawTooltip } from './utils/draw-tooltip/draw-tooltip.js';
46
47
  export { elapsedTime } from './utils/elapsed-time/elapsed-time.js';
47
48
  export { elementById, searchableById, } from './utils/element-by-id/element-by-id.js';
49
+ export { fillTemplate } from './utils/fill-template/fill-template.js';
48
50
  export { deepActiveElement, deepContains, focusableElements, } from './utils/focusable-elements/focusable-elements.js';
49
51
  export { formatNumbers } from './utils/format-numbers/format-numbers.js';
50
52
  export { hostAria } from './utils/host-aria/host-aria.js';
51
53
  export { forgetDisabled, inheritDisabled, } from './utils/inherit-disabled/inherit-disabled.js';
52
54
  export { inlineStep } from './utils/inline-step/inline-step.js';
53
55
  export { isRtl } from './utils/is-rtl/is-rtl.js';
56
+ export { keyClaimed } from './utils/key-claimed/key-claimed.js';
54
57
  export { linePath } from './utils/line-path/line-path.js';
55
58
  export { markRegion } from './utils/mark-region/mark-region.js';
56
59
  export { copiesOf, sameCopies, } from './utils/media-copy/media-copy.js';
@@ -58,6 +61,7 @@ export { onUnlessFalse } from './utils/on-unless-false/on-unless-false.js';
58
61
  export { readColor } from './utils/read-color/read-color.js';
59
62
  export { readRows, readSeries, } from './utils/read-series/read-series.js';
60
63
  export { relativeTime, } from './utils/relative-time/relative-time.js';
64
+ export { reportValues } from './utils/report-values/report-values.js';
61
65
  export { requestChange, } from './utils/request-change/request-change.js';
62
66
  export { resolveActiveItem, } from './utils/resolve-active-item/resolve-active-item.js';
63
67
  export { holdCaret, restoreValue } from './utils/restore-value/restore-value.js';
@@ -65,6 +69,7 @@ export { extent, linear } from './utils/scale/scale.js';
65
69
  export { ticks } from './utils/scale/ticks.js';
66
70
  export { scrollItemIntoView, } from './utils/scroll-item-into-view/scroll-item-into-view.js';
67
71
  export { slotFilled } from './utils/slot-filled/slot-filled.js';
72
+ export { slottedWords } from './utils/slotted-words/slotted-words.js';
68
73
  export { splitValues } from './utils/split-values/split-values.js';
69
74
  export { tellDisclosure } from './utils/tell-disclosure/tell-disclosure.js';
70
75
  export { subscribe } from './utils/ticker/ticker.js';
@@ -17,6 +17,12 @@ export declare class CollectionElement {
17
17
  * Rendered by the component wherever its rows would be.
18
18
  */
19
19
  protected renderStatus(hasContent: boolean): TemplateResult | typeof nothing;
20
+ /**
21
+ * The same words for a screen reader, in a region that is always there.
22
+ *
23
+ * Rendered by the component outside its rows, where a status is allowed.
24
+ */
25
+ protected renderLive(hasContent: boolean): TemplateResult;
20
26
  /** Binds to a rows slot's `slotchange`, and answers `hasRows` from it. */
21
27
  protected countRows(event: Event): void;
22
28
  /** What the rows slot is given, read from the host so a first render has it. */
@@ -43,13 +43,17 @@ export const MixinCollection = (Base) => {
43
43
  }
44
44
  renderStatus(hasContent) {
45
45
  if (this.loading) {
46
- return html `<p part="loading" role="status">${this.loadingText}</p>`;
46
+ return html `<p part="loading">${this.loadingText}</p>`;
47
47
  }
48
48
  if (hasContent) {
49
49
  return nothing;
50
50
  }
51
51
  return html `<p part="empty">${this.emptyText}</p>`;
52
52
  }
53
+ renderLive(hasContent) {
54
+ const said = this.loading ? this.loadingText : hasContent ? '' : this.emptyText;
55
+ return html `<span part="live" role="status">${said}</span>`;
56
+ }
53
57
  }
54
58
  __decorate([
55
59
  property({ attribute: 'empty-text' })
@@ -57,11 +57,11 @@ export const MixinControllerOpen = (Base) => {
57
57
  return this.requestOpen(!this.open);
58
58
  }
59
59
  }
60
- __decorate([
61
- property({ type: Boolean })
62
- ], OpenElement.prototype, "open", null);
63
60
  __decorate([
64
61
  property({ attribute: 'default-open', type: Boolean })
65
62
  ], OpenElement.prototype, "defaultOpen", void 0);
63
+ __decorate([
64
+ property({ type: Boolean })
65
+ ], OpenElement.prototype, "open", null);
66
66
  return OpenElement;
67
67
  };
@@ -40,7 +40,7 @@ export declare class MixinControllerValueInterface {
40
40
  /** Returns the control to the value its markup declared. */
41
41
  formResetCallback(): void;
42
42
  /** Restores the value when the browser brings a form back. */
43
- formStateRestoreCallback(state: string | null): void;
43
+ formStateRestoreCallback(state: string | File | FormData | null): void;
44
44
  }
45
45
  /**
46
46
  * Grafts `value`, `defaultValue`, and the two callbacks that answer a form.
@@ -49,13 +49,14 @@ export const MixinControllerValue = (Base) => {
49
49
  return applied;
50
50
  }
51
51
  formResetCallback() {
52
+ super.formResetCallback();
52
53
  const previous = this.value;
53
54
  this.valueState = undefined;
54
55
  this.requestUpdate('value', previous);
55
56
  }
56
57
  formStateRestoreCallback(state) {
57
58
  const previous = this.value;
58
- this.valueState = state ?? undefined;
59
+ this.valueState = typeof state === 'string' ? state : undefined;
59
60
  this.requestUpdate('value', previous);
60
61
  }
61
62
  }
@@ -16,6 +16,10 @@ export declare class MixinFieldInterface {
16
16
  * control being the family's. See the note on the class member.
17
17
  */
18
18
  protected controlId: string;
19
+ /** The id the label carries, for an element in the template to be named by. */
20
+ protected labelId: string;
21
+ /** Whether a label is drawn at all, in either shape. */
22
+ protected get labeled(): boolean;
19
23
  /**
20
24
  * Help under the control, shown while there is nothing wrong.
21
25
  *
@@ -78,8 +82,15 @@ export declare class MixinFieldInterface {
78
82
  * point its bubble at.
79
83
  */
80
84
  protected reportRequired(empty: boolean, message: string, anchor: string): void;
85
+ /**
86
+ * Hands on the verdict of a native control this renders, with the page's own
87
+ * words where it gave some.
88
+ */
89
+ protected copyValidity(control: HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement): void;
81
90
  /** The message actually shown: the consumer's, or the platform's, or none. */
82
91
  readonly resolvedFeedback: string;
92
+ /** What a reader is told when the field is required and empty, where the page says. */
93
+ valueMissingMessage: string | undefined;
83
94
  }
84
95
  /**
85
96
  * Grafts a label, help text, and a validation message onto a control.
@@ -14,6 +14,7 @@ export const MixinField = (Base) => {
14
14
  constructor() {
15
15
  super(...arguments);
16
16
  this.controlId = 'control';
17
+ this.labelId = 'label';
17
18
  this.feedback = '';
18
19
  this.hint = '';
19
20
  this.label = '';
@@ -21,6 +22,9 @@ export const MixinField = (Base) => {
21
22
  this.touched = false;
22
23
  this.shownFeedback = '';
23
24
  }
25
+ get labeled() {
26
+ return this.label !== '' || Boolean(this.getAttribute('aria-label'));
27
+ }
24
28
  get validates() {
25
29
  return this.constructor.formAssociated === true;
26
30
  }
@@ -44,7 +48,7 @@ export const MixinField = (Base) => {
44
48
  if (this.label === '' && !hidden) {
45
49
  return nothing;
46
50
  }
47
- return html `<span part="label" ?data-hidden=${hidden !== null}
51
+ return html `<span part="label" id=${this.labelId} ?data-hidden=${hidden !== null}
48
52
  ><slot name="label">${this.label || hidden}</slot></span
49
53
  >`;
50
54
  }
@@ -53,7 +57,11 @@ export const MixinField = (Base) => {
53
57
  if (this.label === '' && !hidden) {
54
58
  return nothing;
55
59
  }
56
- return html `<label part="label" for=${this.controlId} ?data-hidden=${hidden !== null}
60
+ return html `<label
61
+ part="label"
62
+ for=${this.controlId}
63
+ id=${this.labelId}
64
+ ?data-hidden=${hidden !== null}
57
65
  ><slot name="label">${this.label || hidden}</slot></label
58
66
  >`;
59
67
  }
@@ -90,19 +98,30 @@ export const MixinField = (Base) => {
90
98
  }
91
99
  reportRequired(empty, message, anchor) {
92
100
  if (this.required && empty) {
93
- this.internals.setValidity({ valueMissing: true }, message, this.partNamed(anchor) ?? undefined);
101
+ this.internals.setValidity({ valueMissing: true }, this.valueMissingMessage || message, this.partNamed(anchor) ?? undefined);
94
102
  }
95
103
  else {
96
104
  this.internals.setValidity({});
97
105
  }
98
106
  this.refreshFeedback();
99
107
  }
108
+ copyValidity(control) {
109
+ const message = control.validity.valueMissing && this.valueMissingMessage
110
+ ? this.valueMissingMessage
111
+ : control.validationMessage;
112
+ this.internals.setValidity(control.validity, message, control);
113
+ }
100
114
  willUpdate(changed) {
101
115
  super.willUpdate(changed);
102
116
  this.shownFeedback = this.resolvedFeedback;
103
117
  this.toggleState('invalid', this.shownFeedback !== '');
104
118
  this.internals.ariaInvalid = this.shownFeedback === '' ? null : 'true';
105
119
  }
120
+ formResetCallback() {
121
+ ;
122
+ Base.prototype.formResetCallback?.call(this);
123
+ this.touched = false;
124
+ }
106
125
  firstUpdated(changed) {
107
126
  super.firstUpdated(changed);
108
127
  const touch = () => {
@@ -124,6 +143,9 @@ export const MixinField = (Base) => {
124
143
  __decorate([
125
144
  property({ reflect: true, type: Boolean })
126
145
  ], FieldElement.prototype, "required", void 0);
146
+ __decorate([
147
+ property({ attribute: 'value-missing-message' })
148
+ ], FieldElement.prototype, "valueMissingMessage", void 0);
127
149
  __decorate([
128
150
  state()
129
151
  ], FieldElement.prototype, "touched", void 0);
@@ -17,6 +17,8 @@ type PickerBase = HelixFormElement & MixinControllerOpenInterface & MixinControl
17
17
  * to this interface keeps the internals private and the build working.
18
18
  */
19
19
  export declare class MixinPickerFieldInterface {
20
+ /** What a reader is told when the field holds something the picker cannot read. */
21
+ badInputMessage: string | undefined;
20
22
  /**
21
23
  * What the label points at and focus lands on.
22
24
  *
@@ -25,6 +27,12 @@ export declare class MixinPickerFieldInterface {
25
27
  * can name.
26
28
  */
27
29
  protected get anchor(): HTMLElement | null;
30
+ /**
31
+ * Each answer the field holds, where it holds several, or `undefined` where
32
+ * its value is one answer. Several are sent one entry each; see
33
+ * `reportValues`.
34
+ */
35
+ protected get answers(): readonly string[] | undefined;
28
36
  /** The text input, or `null` where there is no DOM to hold one. */
29
37
  protected get control(): HTMLInputElement | null;
30
38
  /**
@@ -1,4 +1,12 @@
1
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
2
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
3
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
4
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
5
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
6
+ };
7
+ import { property } from 'lit/decorators.js';
1
8
  import { PopoverController } from '../../controllers/popover-controller/popover-controller.js';
9
+ import { reportValues } from '../../utils/report-values/report-values.js';
2
10
  export const MixinPickerField = (Base) => {
3
11
  class PickerFieldElement extends Base {
4
12
  constructor() {
@@ -36,6 +44,9 @@ export const MixinPickerField = (Base) => {
36
44
  get display() {
37
45
  return this.value;
38
46
  }
47
+ get answers() {
48
+ return undefined;
49
+ }
39
50
  validate() {
40
51
  return '';
41
52
  }
@@ -47,14 +58,20 @@ export const MixinPickerField = (Base) => {
47
58
  this.sync();
48
59
  }
49
60
  sync() {
50
- this.internals.setFormValue(this.value);
61
+ const answers = this.answers;
62
+ if (answers === undefined) {
63
+ this.internals.setFormValue(this.value);
64
+ }
65
+ else {
66
+ reportValues(this.internals, this.name, answers);
67
+ }
51
68
  const control = this.control;
52
69
  if (!control) {
53
70
  return;
54
71
  }
55
72
  const message = this.validate();
56
73
  if (message === '') {
57
- this.internals.setValidity(control.validity, control.validationMessage, control);
74
+ this.copyValidity(control);
58
75
  }
59
76
  else {
60
77
  this.internals.setValidity({ badInput: true }, message, control);
@@ -78,5 +95,8 @@ export const MixinPickerField = (Base) => {
78
95
  this.sync();
79
96
  }
80
97
  }
98
+ __decorate([
99
+ property({ attribute: 'bad-input-message' })
100
+ ], PickerFieldElement.prototype, "badInputMessage", void 0);
81
101
  return PickerFieldElement;
82
102
  };
@@ -37,7 +37,6 @@ export const MixinTokens = (Base) => {
37
37
  return html `<button
38
38
  part="rest"
39
39
  type="button"
40
- aria-expanded=${this.expanded ? 'true' : 'false'}
41
40
  aria-label=${this.expanded ? this.collapseLabel : this.expandLabel}
42
41
  @click=${this.toggleExpanded}
43
42
  >${this.expanded ? drawIcon(caretUp) : `+${over}`}</button
@@ -1,7 +1,7 @@
1
1
  import { type LitElement, nothing, type TemplateResult } from 'lit';
2
2
  import { FullscreenController } from '../../controllers/fullscreen-controller/fullscreen-controller.js';
3
3
  import { IdleController } from '../../controllers/idle-controller/idle-controller.js';
4
- import { type ViewportPanDetail, type ViewportZoomDetail } from '../../events/events.js';
4
+ import type { ViewportPanDetail, ViewportZoomDetail } from '../../events/events.js';
5
5
  type Constructor<T> = new (...args: any[]) => T;
6
6
  /** Which way a press or a key asked the surface to go. */
7
7
  export type ViewportWay = 'up' | 'down' | 'start' | 'end';
@@ -19,10 +19,16 @@ export declare class ViewportElement {
19
19
  fullscreenLabel: string;
20
20
  /** How long the reader may be still before the chrome hides, in milliseconds. */
21
21
  idleDelay: number;
22
- /** What one pan control is called, with `{way}` filled in. */
23
- panLabel: string;
22
+ /** What the control that pans down is called. */
23
+ panDownLabel: string;
24
+ /** What the control that pans left is called. */
25
+ panLeftLabel: string;
26
+ /** What the control that pans right is called. */
27
+ panRightLabel: string;
24
28
  /** How far one press moves the view, as a fraction of what is on screen. */
25
29
  panStep: number;
30
+ /** What the control that pans up is called. */
31
+ panUpLabel: string;
26
32
  /** What the control that zooms in is called. */
27
33
  zoomInLabel: string;
28
34
  /** What the control that zooms out is called. */
@@ -43,14 +49,20 @@ export declare class ViewportElement {
43
49
  protected readonly fullscreen: FullscreenController;
44
50
  /** The key relay, bound by the family's own frame. */
45
51
  protected handleKeydown: (event: KeyboardEvent) => void;
46
- /** What the group of controls is called, for a family that is not a map. */
47
- protected get controlsLabel(): string;
52
+ /** What the group of controls is called, where the page says. */
53
+ controlsLabel: string | undefined;
54
+ /** What the group of controls is called where the page does not say, which each family says for itself. */
55
+ protected get restingControlsLabel(): string;
48
56
  /** The pan event, built and not dispatched, for a family that names its own. */
49
57
  protected panEvent(detail: ViewportPanDetail): Event;
50
58
  /** The zoom event, built and not dispatched, for a family that names its own. */
51
59
  protected zoomEvent(detail: ViewportZoomDetail): Event;
52
60
  /** The controls, or nothing where the surface keeps none of them. */
53
61
  protected renderControls(): TemplateResult | typeof nothing;
62
+ /** The drawn group of controls, where there is one, for a family that measures it. */
63
+ protected get controlsElement(): Element | null;
64
+ /** Which controls the surface draws while `controls` is absent. */
65
+ protected get restingControls(): string;
54
66
  }
55
67
  /**
56
68
  * Grafts a pan-and-zoom surface: its controls, its keys, and the way out.
@@ -8,18 +8,20 @@ import { html, nothing } from 'lit';
8
8
  import { property, state } from 'lit/decorators.js';
9
9
  import { FullscreenController } from '../../controllers/fullscreen-controller/fullscreen-controller.js';
10
10
  import { IdleController } from '../../controllers/idle-controller/idle-controller.js';
11
- import { HelixMapPanEvent, HelixMapZoomEvent, } from '../../events/events.js';
12
11
  import { inlineStep } from '../../utils/inline-step/inline-step.js';
12
+ import { isRtl } from '../../utils/is-rtl/is-rtl.js';
13
13
  export const MixinViewport = (Base) => {
14
14
  class Viewport extends Base {
15
15
  constructor() {
16
16
  super(...arguments);
17
17
  this.fullscreenLabel = 'Fill the screen';
18
18
  this.idleDelay = 0;
19
- this.panLabel = 'Pan {way}';
19
+ this.panDownLabel = 'Pan down';
20
+ this.panLeftLabel = 'Pan left';
21
+ this.panRightLabel = 'Pan right';
20
22
  this.panStep = 0.25;
23
+ this.panUpLabel = 'Pan up';
21
24
  this.zoomInLabel = 'Zoom in';
22
- this.zoomOutLabel = 'Zoom out';
23
25
  this.resting = false;
24
26
  this.filled = false;
25
27
  this.fullscreen = new FullscreenController(this, (full) => {
@@ -28,12 +30,16 @@ export const MixinViewport = (Base) => {
28
30
  this.idle = new IdleController(this, () => this.idleDelay, (resting) => {
29
31
  this.resting = resting;
30
32
  });
33
+ this.zoomOutLabel = 'Zoom out';
31
34
  this.handleKeydown = (event) => {
32
35
  const target = event.target;
33
36
  const own = target?.closest('[part="region"], [part~="controls"]');
34
37
  if (!own || !this.renderRoot.contains(own)) {
35
38
  return;
36
39
  }
40
+ if (target !== own && !own.matches('[part~="controls"]')) {
41
+ return;
42
+ }
37
43
  const along = inlineStep(this, event.key);
38
44
  if (along === 1) {
39
45
  event.preventDefault();
@@ -61,14 +67,14 @@ export const MixinViewport = (Base) => {
61
67
  }
62
68
  };
63
69
  }
64
- get controlsLabel() {
70
+ get restingControlsLabel() {
65
71
  return 'View controls';
66
72
  }
67
- panEvent(detail) {
68
- return new HelixMapPanEvent(detail);
73
+ panEvent(_detail) {
74
+ throw new Error(`<${this.localName}> composes MixinViewport and must implement panEvent(), the surface naming no event of its own.`);
69
75
  }
70
- zoomEvent(detail) {
71
- return new HelixMapZoomEvent(detail);
76
+ zoomEvent(_detail) {
77
+ throw new Error(`<${this.localName}> composes MixinViewport and must implement zoomEvent(), the surface naming no event of its own.`);
72
78
  }
73
79
  pan(to) {
74
80
  this.dispatchEvent(this.panEvent({ by: this.panStep, to }));
@@ -79,16 +85,45 @@ export const MixinViewport = (Base) => {
79
85
  toggleFullscreen() {
80
86
  void this.fullscreen.toggle();
81
87
  }
88
+ get restingControls() {
89
+ return 'pan zoom fullscreen';
90
+ }
82
91
  draws(group) {
83
- return this.controls === undefined ? true : this.controls.split(/\s+/).includes(group);
92
+ return (this.controls ?? this.restingControls).split(/\s+/).includes(group);
93
+ }
94
+ sideOf(way) {
95
+ if (way !== 'start' && way !== 'end') {
96
+ return way;
97
+ }
98
+ return (way === 'start') !== this.rightToLeft ? 'left' : 'right';
99
+ }
100
+ get rightToLeft() {
101
+ const element = this;
102
+ return !element.hydrating && isRtl(this);
84
103
  }
85
104
  named(way) {
86
- const spoken = way === 'start' ? 'left' : way === 'end' ? 'right' : way;
87
- return this.panLabel.replace('{way}', spoken);
105
+ const side = this.sideOf(way);
106
+ return side === 'up'
107
+ ? this.panUpLabel
108
+ : side === 'down'
109
+ ? this.panDownLabel
110
+ : side === 'left'
111
+ ? this.panLeftLabel
112
+ : this.panRightLabel;
113
+ }
114
+ glyph(way) {
115
+ return { down: '↓', left: '←', right: '→', up: '↑' }[this.sideOf(way)] ?? '';
88
116
  }
89
117
  firstUpdated(changed) {
90
118
  super.firstUpdated(changed);
91
119
  this.idle.stir();
120
+ const element = this;
121
+ if (element.serverRendered && isRtl(this)) {
122
+ this.requestUpdate();
123
+ }
124
+ }
125
+ get controlsElement() {
126
+ return this.renderRoot?.querySelector('[part="controls"]') ?? null;
92
127
  }
93
128
  renderControls() {
94
129
  const pan = this.draws('pan');
@@ -97,19 +132,19 @@ export const MixinViewport = (Base) => {
97
132
  if (!pan && !zoom && !fullscreen) {
98
133
  return nothing;
99
134
  }
100
- return html `<div part="controls" role="group" aria-label=${this.controlsLabel}>
135
+ return html `<div part="controls" role="group" aria-label=${this.controlsLabel ?? this.restingControlsLabel}>
101
136
  ${pan
102
137
  ? html `<button part="pan-up" aria-label=${this.named('up')} type="button" @click=${() => this.pan('up')}>
103
- ↑
138
+ ${this.glyph('up')}
104
139
  </button>
105
140
  <button part="pan-start" aria-label=${this.named('start')} type="button" @click=${() => this.pan('start')}>
106
- ←
141
+ ${this.glyph('start')}
107
142
  </button>
108
143
  <button part="pan-end" aria-label=${this.named('end')} type="button" @click=${() => this.pan('end')}>
109
- →
144
+ ${this.glyph('end')}
110
145
  </button>
111
146
  <button part="pan-down" aria-label=${this.named('down')} type="button" @click=${() => this.pan('down')}>
112
- ↓
147
+ ${this.glyph('down')}
113
148
  </button>`
114
149
  : nothing}
115
150
  ${zoom
@@ -137,6 +172,9 @@ export const MixinViewport = (Base) => {
137
172
  __decorate([
138
173
  property({ reflect: true })
139
174
  ], Viewport.prototype, "controls", void 0);
175
+ __decorate([
176
+ property({ attribute: 'controls-label' })
177
+ ], Viewport.prototype, "controlsLabel", void 0);
140
178
  __decorate([
141
179
  property({ attribute: 'controls-placement', reflect: true })
142
180
  ], Viewport.prototype, "controlsPlacement", void 0);
@@ -147,22 +185,31 @@ export const MixinViewport = (Base) => {
147
185
  property({ attribute: 'idle-delay', type: Number })
148
186
  ], Viewport.prototype, "idleDelay", void 0);
149
187
  __decorate([
150
- property({ attribute: 'pan-label' })
151
- ], Viewport.prototype, "panLabel", void 0);
188
+ property({ attribute: 'pan-down-label' })
189
+ ], Viewport.prototype, "panDownLabel", void 0);
190
+ __decorate([
191
+ property({ attribute: 'pan-left-label' })
192
+ ], Viewport.prototype, "panLeftLabel", void 0);
193
+ __decorate([
194
+ property({ attribute: 'pan-right-label' })
195
+ ], Viewport.prototype, "panRightLabel", void 0);
152
196
  __decorate([
153
197
  property({ attribute: 'pan-step', type: Number })
154
198
  ], Viewport.prototype, "panStep", void 0);
199
+ __decorate([
200
+ property({ attribute: 'pan-up-label' })
201
+ ], Viewport.prototype, "panUpLabel", void 0);
155
202
  __decorate([
156
203
  property({ attribute: 'zoom-in-label' })
157
204
  ], Viewport.prototype, "zoomInLabel", void 0);
158
- __decorate([
159
- property({ attribute: 'zoom-out-label' })
160
- ], Viewport.prototype, "zoomOutLabel", void 0);
161
205
  __decorate([
162
206
  state()
163
207
  ], Viewport.prototype, "resting", void 0);
164
208
  __decorate([
165
209
  state()
166
210
  ], Viewport.prototype, "filled", void 0);
211
+ __decorate([
212
+ property({ attribute: 'zoom-out-label' })
213
+ ], Viewport.prototype, "zoomOutLabel", void 0);
167
214
  return Viewport;
168
215
  };
@@ -0,0 +1,13 @@
1
+ /**
2
+ * A label with its `{placeholders}` filled in.
3
+ *
4
+ * **The one way every translatable sentence here is written**: a property holds
5
+ * the whole sentence, and the numbers and names go in where it says. A
6
+ * sentence built from pieces in code puts the pieces in English order, and
7
+ * another language may want them the other way round.
8
+ *
9
+ * Every occurrence is filled, so a sentence can name the same thing twice. A
10
+ * placeholder with no value is left as written rather than emptied, which
11
+ * shows up on the page as the mistake it is.
12
+ */
13
+ export declare const fillTemplate: (template: string, values: Record<string, string | number>) => string;
@@ -0,0 +1 @@
1
+ export const fillTemplate = (template, values) => Object.entries(values).reduce((out, [key, value]) => out.replaceAll(`{${key}}`, () => String(value)), template);
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Whether a press was already the business of whatever it landed on.
3
+ *
4
+ * **For a container that binds keys over content it did not write**: a toolbar,
5
+ * a viewer, a drilldown. Some of what they hold uses the same keys for itself,
6
+ * and moving the container regardless makes an input inside it uneditable:
7
+ * ArrowRight moves somewhere else, and `preventDefault` means the caret never
8
+ * moves at all.
9
+ *
10
+ * `composedPath()[0]` rather than `event.target`, which is retargeted to the
11
+ * control's host at the shadow boundary and cannot tell a text field inside a
12
+ * component from the component itself.
13
+ *
14
+ * `defaultPrevented` first, because a control that has already answered the
15
+ * press has said so in the way the platform provides, and a foreign control
16
+ * this package has never heard of has no other way to say it.
17
+ */
18
+ export declare const keyClaimed: (event: KeyboardEvent) => boolean;