@nldd/design-system 0.8.87 → 0.8.89
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/README.md +2 -2
- package/custom-elements.json +305 -66
- package/dist/components/components.js +1007 -218
- package/dist/components/content/image/image.d.ts +23 -3
- package/dist/components/content/image/image.d.ts.map +1 -1
- package/dist/components/content/image/image.js +74 -17
- package/dist/components/content/image/image.js.map +1 -1
- package/dist/components/content/image/image.template.js +1 -1
- package/dist/components/content/image/image.template.js.map +1 -1
- package/dist/components/{dist-LX8LuMac.js → dist-BLaE_n_F.js} +1 -1
- package/dist/components/{dist-CX3DK4i7.js → dist-BeymuiGt.js} +3 -3
- package/dist/components/{dist-CbCO0WCd.js → dist-CENYEz7C.js} +1 -1
- package/dist/components/{dist-B_Mnmrj4.js → dist-CYLM5ciE.js} +1 -1
- package/dist/components/{dist-fIxe0l7k.js → dist-DHA-0RyT.js} +91 -34
- package/dist/components/{dist-CgDuh26K.js → dist-D_ckX1sK.js} +1 -1
- package/dist/components/{dist-eVFegyjM.js → dist-Dh_b9r4M.js} +1 -1
- package/dist/components/{dist-DXZ-cFAl.js → dist-DkZlotgT.js} +2 -2
- package/dist/components/{dist-CSrO_mYe.js → dist-TCqV9dho.js} +1 -1
- package/dist/components/{dist-ByH8hQze.js → dist-Y54Lma86.js} +2 -2
- package/dist/components/{dist-CRwuSgMM.js → dist-yyE8P2O6.js} +2 -2
- package/dist/components/inputs/radio-button/radio-button.d.ts +58 -18
- package/dist/components/inputs/radio-button/radio-button.d.ts.map +1 -1
- package/dist/components/inputs/radio-button/radio-button.js +136 -29
- package/dist/components/inputs/radio-button/radio-button.js.map +1 -1
- package/dist/components/inputs/radio-button/radio-button.styles.d.ts.map +1 -1
- package/dist/components/inputs/radio-button/radio-button.styles.js +17 -23
- package/dist/components/inputs/radio-button/radio-button.styles.js.map +1 -1
- package/dist/components/inputs/radio-button/radio-button.template.d.ts.map +1 -1
- package/dist/components/inputs/radio-button/radio-button.template.js +13 -10
- package/dist/components/inputs/radio-button/radio-button.template.js.map +1 -1
- package/dist/components/inputs/radio-button-field/radio-button-field.d.ts +65 -18
- package/dist/components/inputs/radio-button-field/radio-button-field.d.ts.map +1 -1
- package/dist/components/inputs/radio-button-field/radio-button-field.js +151 -29
- package/dist/components/inputs/radio-button-field/radio-button-field.js.map +1 -1
- package/dist/components/inputs/radio-button-field/radio-button-field.styles.d.ts.map +1 -1
- package/dist/components/inputs/radio-button-field/radio-button-field.styles.js +17 -0
- package/dist/components/inputs/radio-button-field/radio-button-field.styles.js.map +1 -1
- package/dist/components/inputs/radio-button-field/radio-button-field.template.d.ts.map +1 -1
- package/dist/components/inputs/radio-button-field/radio-button-field.template.js +21 -12
- package/dist/components/inputs/radio-button-field/radio-button-field.template.js.map +1 -1
- package/dist/components/inputs/radio-button-group/radio-button-group.d.ts +3 -0
- package/dist/components/inputs/radio-button-group/radio-button-group.d.ts.map +1 -1
- package/dist/components/inputs/radio-button-group/radio-button-group.js +16 -4
- package/dist/components/inputs/radio-button-group/radio-button-group.js.map +1 -1
- package/dist/components/inputs/segmented-control/segmented-control.d.ts +46 -13
- package/dist/components/inputs/segmented-control/segmented-control.d.ts.map +1 -1
- package/dist/components/inputs/segmented-control/segmented-control.js +145 -16
- package/dist/components/inputs/segmented-control/segmented-control.js.map +1 -1
- package/dist/components/inputs/segmented-control/segmented-control.styles.d.ts.map +1 -1
- package/dist/components/inputs/segmented-control/segmented-control.styles.js +16 -0
- package/dist/components/inputs/segmented-control/segmented-control.styles.js.map +1 -1
- package/dist/components/inputs/segmented-control/segmented-control.template.d.ts.map +1 -1
- package/dist/components/inputs/segmented-control/segmented-control.template.js +47 -24
- package/dist/components/inputs/segmented-control/segmented-control.template.js.map +1 -1
- package/dist/components/inputs/text-editor/text-editor.cursor.d.ts.map +1 -1
- package/dist/components/inputs/text-editor/text-editor.cursor.js +14 -1
- package/dist/components/inputs/text-editor/text-editor.cursor.js.map +1 -1
- package/dist/components/inputs/text-editor/text-editor.mentions.d.ts.map +1 -1
- package/dist/components/inputs/text-editor/text-editor.mentions.js +3 -1
- package/dist/components/inputs/text-editor/text-editor.mentions.js.map +1 -1
- package/dist/components/inputs/toggle-button/toggle-button.d.ts +36 -2
- package/dist/components/inputs/toggle-button/toggle-button.d.ts.map +1 -1
- package/dist/components/inputs/toggle-button/toggle-button.js +116 -3
- package/dist/components/inputs/toggle-button/toggle-button.js.map +1 -1
- package/dist/components/inputs/toggle-button/toggle-button.styles.d.ts.map +1 -1
- package/dist/components/inputs/toggle-button/toggle-button.styles.js +17 -1
- package/dist/components/inputs/toggle-button/toggle-button.styles.js.map +1 -1
- package/dist/components/inputs/toggle-button/toggle-button.template.d.ts.map +1 -1
- package/dist/components/inputs/toggle-button/toggle-button.template.js +25 -2
- package/dist/components/inputs/toggle-button/toggle-button.template.js.map +1 -1
- package/dist/components/inputs/toggle-button-group/toggle-button-group.d.ts +4 -0
- package/dist/components/inputs/toggle-button-group/toggle-button-group.d.ts.map +1 -1
- package/dist/components/inputs/toggle-button-group/toggle-button-group.js +21 -2
- package/dist/components/inputs/toggle-button-group/toggle-button-group.js.map +1 -1
- package/dist/components/layout/page/page.d.ts +23 -5
- package/dist/components/layout/page/page.d.ts.map +1 -1
- package/dist/components/layout/page/page.js +39 -34
- package/dist/components/layout/page/page.js.map +1 -1
- package/dist/components/layout/page/page.styles.d.ts.map +1 -1
- package/dist/components/layout/page/page.styles.js +2 -0
- package/dist/components/layout/page/page.styles.js.map +1 -1
- package/dist/components/layout/sheet/sheet.template.d.ts.map +1 -1
- package/dist/components/layout/sheet/sheet.template.js +3 -0
- package/dist/components/layout/sheet/sheet.template.js.map +1 -1
- package/dist/components/layout/window/window.template.d.ts.map +1 -1
- package/dist/components/layout/window/window.template.js +3 -0
- package/dist/components/layout/window/window.template.js.map +1 -1
- package/dist/components/lists-and-tables/list/list.d.ts +1 -1
- package/dist/components/lists-and-tables/list/list.d.ts.map +1 -1
- package/dist/components/lists-and-tables/list/list.js +4 -3
- package/dist/components/lists-and-tables/list/list.js.map +1 -1
- package/dist/components/lists-and-tables/list/list.template.d.ts.map +1 -1
- package/dist/components/lists-and-tables/list/list.template.js +15 -10
- package/dist/components/lists-and-tables/list/list.template.js.map +1 -1
- package/dist/components/navigation/menu-bar/menu-bar.d.ts +4 -2
- package/dist/components/navigation/menu-bar/menu-bar.d.ts.map +1 -1
- package/dist/components/navigation/menu-bar/menu-bar.js +4 -2
- package/dist/components/navigation/menu-bar/menu-bar.js.map +1 -1
- package/dist/components/status-and-feedback/modal-dialog/modal-dialog.template.d.ts.map +1 -1
- package/dist/components/status-and-feedback/modal-dialog/modal-dialog.template.js +3 -0
- package/dist/components/status-and-feedback/modal-dialog/modal-dialog.template.js.map +1 -1
- package/dist/components/status-and-feedback/notification/notification-region.d.ts +4 -0
- package/dist/components/status-and-feedback/notification/notification-region.d.ts.map +1 -1
- package/dist/components/status-and-feedback/notification/notification-region.js +161 -19
- package/dist/components/status-and-feedback/notification/notification-region.js.map +1 -1
- package/dist/components/status-and-feedback/notification/notification.d.ts +17 -0
- package/dist/components/status-and-feedback/notification/notification.d.ts.map +1 -1
- package/dist/components/status-and-feedback/notification/notification.js +33 -0
- package/dist/components/status-and-feedback/notification/notification.js.map +1 -1
- package/dist/components/status-and-feedback/notification/notification.styles.d.ts.map +1 -1
- package/dist/components/status-and-feedback/notification/notification.styles.js +6 -0
- package/dist/components/status-and-feedback/notification/notification.styles.js.map +1 -1
- package/dist/css/variables.css +2 -2
- package/dist/nldd.min.js +629 -540
- package/dist/types/vue.d.ts +24 -20
- package/dist/utilities/described-by-mixin.d.ts +2 -2
- package/dist/utilities/described-by-mixin.d.ts.map +1 -1
- package/dist/utilities/described-by-mixin.js +11 -6
- package/dist/utilities/described-by-mixin.js.map +1 -1
- package/dist/utilities/radio-grouping.d.ts +66 -0
- package/dist/utilities/radio-grouping.d.ts.map +1 -0
- package/dist/utilities/radio-grouping.js +133 -0
- package/dist/utilities/radio-grouping.js.map +1 -0
- package/dist/utilities/radio-position.d.ts +20 -0
- package/dist/utilities/radio-position.d.ts.map +1 -0
- package/dist/utilities/radio-position.js +11 -0
- package/dist/utilities/radio-position.js.map +1 -0
- package/package.json +10 -10
|
@@ -8,9 +8,9 @@ import { t as NLDDIcon } from "./icon-C_zwJZ_g.js";
|
|
|
8
8
|
import { n as isPointerMode, r as isTouchMode, t as isKeyboardMode } from "./input-modality-DhTeUxKi.js";
|
|
9
9
|
import { t as breakpoints } from "./breakpoints-CkxsiRgH.js";
|
|
10
10
|
import { t as VisibilityMixin } from "./visibility-mixin-BU1vIMW7.js";
|
|
11
|
-
import { $ as layer, A as syntaxTree, B as Tree, C as indentNodeProp, Ct as findClusterBreak, D as matchBrackets, E as languageDataProp, F as NodeProp, G as RectangleMarker, H as Decoration, I as NodeSet, J as drawSelection, K as ViewPlugin, L as NodeType, M as styleTags, N as tags, Q as keymap, S as getIndentation, St as countColumn, T as indentUnit, U as Direction, V as parseMixed, W as EditorView, X as getDrawSelectionConfig, Y as dropCursor, _t as Text, a as HighlightStyle, at as ChangeDesc, b as foldService, c as Language, ct as EditorSelection, d as ParseContext, et as lineNumbers, f as StreamLanguage, ft as Prec, gt as StateField, h as defineLanguageFacet, ht as StateEffect, it as Annotation, j as Tag, k as syntaxHighlighting, l as LanguageDescription, lt as EditorState, nt as placeholder, o as IndentContext, ot as ChangeSet, pt as RangeSetBuilder, q as WidgetType, st as Compartment, u as LanguageSupport, ut as Facet, vt as Transaction, w as indentString, x as getIndentUnit, xt as combineConfig, y as foldNodeProp, z as Parser } from "./dist-
|
|
12
|
-
import { i as completionStatus, n as autocompletion, s as startCompletion, t as CompletionContext } from "./dist-
|
|
13
|
-
import { n as html$2, r as htmlCompletionSource } from "./dist-
|
|
11
|
+
import { $ as layer, A as syntaxTree, B as Tree, C as indentNodeProp, Ct as findClusterBreak, D as matchBrackets, E as languageDataProp, F as NodeProp, G as RectangleMarker, H as Decoration, I as NodeSet, J as drawSelection, K as ViewPlugin, L as NodeType, M as styleTags, N as tags, Q as keymap, S as getIndentation, St as countColumn, T as indentUnit, U as Direction, V as parseMixed, W as EditorView, X as getDrawSelectionConfig, Y as dropCursor, _t as Text, a as HighlightStyle, at as ChangeDesc, b as foldService, c as Language, ct as EditorSelection, d as ParseContext, et as lineNumbers, f as StreamLanguage, ft as Prec, gt as StateField, h as defineLanguageFacet, ht as StateEffect, it as Annotation, j as Tag, k as syntaxHighlighting, l as LanguageDescription, lt as EditorState, nt as placeholder, o as IndentContext, ot as ChangeSet, pt as RangeSetBuilder, q as WidgetType, st as Compartment, u as LanguageSupport, ut as Facet, vt as Transaction, w as indentString, x as getIndentUnit, xt as combineConfig, y as foldNodeProp, z as Parser } from "./dist-DHA-0RyT.js";
|
|
12
|
+
import { i as completionStatus, n as autocompletion, s as startCompletion, t as CompletionContext } from "./dist-D_ckX1sK.js";
|
|
13
|
+
import { n as html$2, r as htmlCompletionSource } from "./dist-BeymuiGt.js";
|
|
14
14
|
import { LitElement, css, html, nothing, svg, unsafeCSS } from "lit";
|
|
15
15
|
import { customElement, property, query, state } from "lit/decorators.js";
|
|
16
16
|
import { unsafeHTML } from "lit/directives/unsafe-html.js";
|
|
@@ -753,9 +753,9 @@ var nlddButtonTranslations = { "components.button.opens-in-new-tab-label": "Open
|
|
|
753
753
|
* `ariaDescribedByElements` takes the elements themselves and crosses the
|
|
754
754
|
* boundary.
|
|
755
755
|
*/
|
|
756
|
-
function applyDescribedBy(target, elements) {
|
|
756
|
+
function applyDescribedBy(target, elements, onSelf = false) {
|
|
757
757
|
if (!target) return;
|
|
758
|
-
if ("describedByElements" in target) {
|
|
758
|
+
if (!onSelf && "describedByElements" in target) {
|
|
759
759
|
target.describedByElements = [...elements];
|
|
760
760
|
return;
|
|
761
761
|
}
|
|
@@ -774,9 +774,9 @@ function applyDescribedBy(target, elements) {
|
|
|
774
774
|
* A component that wraps another one hands the state on, the same way it hands
|
|
775
775
|
* on the description.
|
|
776
776
|
*/
|
|
777
|
-
function applyInvalid(target, invalid) {
|
|
777
|
+
function applyInvalid(target, invalid, onSelf = false) {
|
|
778
778
|
if (!target) return;
|
|
779
|
-
if ("invalid" in target) {
|
|
779
|
+
if (!onSelf && "invalid" in target) {
|
|
780
780
|
target.invalid = invalid;
|
|
781
781
|
return;
|
|
782
782
|
}
|
|
@@ -804,9 +804,10 @@ function DescribedBy(Base) {
|
|
|
804
804
|
this.describedByElements = [];
|
|
805
805
|
this.addController({ hostUpdated: () => {
|
|
806
806
|
const target = this.describedTarget();
|
|
807
|
-
|
|
807
|
+
const onSelf = target === this;
|
|
808
|
+
applyDescribedBy(target, this.describedByElements, onSelf);
|
|
808
809
|
const invalid = this.invalid;
|
|
809
|
-
if (invalid !== void 0) applyInvalid(target, invalid);
|
|
810
|
+
if (invalid !== void 0) applyInvalid(target, invalid, onSelf);
|
|
810
811
|
} });
|
|
811
812
|
}
|
|
812
813
|
/**
|
|
@@ -8641,11 +8642,12 @@ var template$14 = ({ itemsLabel, hasToolbar, type, isEmpty, hasItems, hasEmptySt
|
|
|
8641
8642
|
const skipItemsLabel = isNavigation || isListbox;
|
|
8642
8643
|
const itemsRole = isListbox ? "listbox" : type === "tree" ? "tree" : type === "radiogroup" ? "radiogroup" : "list";
|
|
8643
8644
|
const isNoResults = isEmpty && hasItems;
|
|
8645
|
+
const untouched = isListbox && listbox.searchValue === "";
|
|
8644
8646
|
const noResultsFallsBack = isNoResults && !hasNoResultsState && hasEmptyState;
|
|
8645
|
-
const showNoResults = isNoResults && hasNoResultsState && !
|
|
8646
|
-
const showEmpty = !isNoResults && isEmpty && hasEmptyState || noResultsFallsBack && !
|
|
8647
|
+
const showNoResults = isNoResults && hasNoResultsState && !untouched;
|
|
8648
|
+
const showEmpty = (!isNoResults && isEmpty && hasEmptyState || noResultsFallsBack) && !untouched;
|
|
8647
8649
|
const showMain = !isEmpty || showEmpty || showNoResults;
|
|
8648
|
-
const showControls = hasItems;
|
|
8650
|
+
const showControls = hasItems || isListbox;
|
|
8649
8651
|
return html`
|
|
8650
8652
|
<div class="list">
|
|
8651
8653
|
<div class="list__header">
|
|
@@ -9128,7 +9130,7 @@ var NLDDList = (_NLDDList2 = class NLDDList extends LitElement {
|
|
|
9128
9130
|
this._hasItems = items.length > 0;
|
|
9129
9131
|
this._hasEmptyState = !!this.querySelector(":scope > [slot=\"empty\"]");
|
|
9130
9132
|
this._hasNoResultsState = !!this.querySelector(":scope > [slot=\"no-results\"]");
|
|
9131
|
-
this.classList.toggle("is-blank", this._isEmpty && !this._hasItems && !this._hasEmptyState);
|
|
9133
|
+
this.classList.toggle("is-blank", this._isEmpty && !this._hasItems && !this._hasEmptyState && this.type !== "listbox");
|
|
9132
9134
|
this._warnSilentEmpty();
|
|
9133
9135
|
}
|
|
9134
9136
|
/**
|
|
@@ -11580,16 +11582,16 @@ var nlddCodeMirrorTheme = [baseTheme, syntaxHighlighting(highlightStyle)];
|
|
|
11580
11582
|
//#endregion
|
|
11581
11583
|
//#region src/utilities/codemirror/languages.ts
|
|
11582
11584
|
var loaders = {
|
|
11583
|
-
json: async () => (await import("./dist-
|
|
11584
|
-
yaml: async () => (await import("./dist-
|
|
11585
|
-
javascript: async () => (await import("./dist-
|
|
11586
|
-
typescript: async () => (await import("./dist-
|
|
11587
|
-
css: async () => (await import("./dist-
|
|
11588
|
-
html: async () => (await import("./dist-
|
|
11589
|
-
xml: async () => (await import("./dist-
|
|
11590
|
-
python: async () => (await import("./dist-
|
|
11591
|
-
rust: async () => (await import("./dist-
|
|
11592
|
-
sql: async () => (await import("./dist-
|
|
11585
|
+
json: async () => (await import("./dist-BLaE_n_F.js")).json(),
|
|
11586
|
+
yaml: async () => (await import("./dist-TCqV9dho.js")).yaml(),
|
|
11587
|
+
javascript: async () => (await import("./dist-Y54Lma86.js").then((n) => n.t)).javascript(),
|
|
11588
|
+
typescript: async () => (await import("./dist-Y54Lma86.js").then((n) => n.t)).javascript({ typescript: true }),
|
|
11589
|
+
css: async () => (await import("./dist-CYLM5ciE.js").then((n) => n.r)).css(),
|
|
11590
|
+
html: async () => (await import("./dist-BeymuiGt.js").then((n) => n.t)).html(),
|
|
11591
|
+
xml: async () => (await import("./dist-Dh_b9r4M.js")).xml(),
|
|
11592
|
+
python: async () => (await import("./dist-yyE8P2O6.js")).python(),
|
|
11593
|
+
rust: async () => (await import("./dist-CENYEz7C.js")).rust(),
|
|
11594
|
+
sql: async () => (await import("./dist-DkZlotgT.js")).sql(),
|
|
11593
11595
|
markdown: async () => (await Promise.resolve().then(() => dist_exports)).markdown(),
|
|
11594
11596
|
bash: async () => new LanguageSupport(StreamLanguage.define((await import("./shell-C61GCsJA.js")).shell)),
|
|
11595
11597
|
toml: async () => new LanguageSupport(StreamLanguage.define((await import("./toml-C2HBbPgG.js")).toml)),
|
|
@@ -12575,7 +12577,7 @@ function imageTemplate(component) {
|
|
|
12575
12577
|
<div class=${mediaClasses}
|
|
12576
12578
|
style=${styleMap(mediaStyles)}
|
|
12577
12579
|
>
|
|
12578
|
-
<slot>${fallbackImg}</slot>
|
|
12580
|
+
<slot @slotchange=${component._onMediaSlotChange}>${fallbackImg}</slot>
|
|
12579
12581
|
${errorOverlay}
|
|
12580
12582
|
${liveRegion}
|
|
12581
12583
|
</div>
|
|
@@ -12616,7 +12618,7 @@ var nlddImageTranslations = {
|
|
|
12616
12618
|
* @element nldd-image
|
|
12617
12619
|
*
|
|
12618
12620
|
* @attr {string} src - Image URL
|
|
12619
|
-
* @attr {string} alt - Alt text
|
|
12621
|
+
* @attr {string} alt - Alt text for the image from `src`. Required unless `decorative`; slotted media carries its own.
|
|
12620
12622
|
* @attr {string} srcset - Responsive source set
|
|
12621
12623
|
* @attr {string} sizes - Source sizes hint
|
|
12622
12624
|
* @attr {number|'full'} width - Display width. `full` (default) fills the parent.
|
|
@@ -12653,7 +12655,7 @@ var nlddImageTranslations = {
|
|
|
12653
12655
|
* @attr {object} translations - Override translation keys (e.g. the message
|
|
12654
12656
|
* shown when the image fails to load); unset keys fall back to Dutch.
|
|
12655
12657
|
*
|
|
12656
|
-
* @slot - Custom `<img>`, `<picture>` or inline `<svg>` (overrides the src-based default). An inline svg keeps its own colors and scales by its viewBox, so a drawing gets the same box, ratio and caption as a photo.
|
|
12658
|
+
* @slot - Custom `<img>`, `<picture>` or inline `<svg>` (overrides the src-based default). An inline svg keeps its own colors and scales by its viewBox, so a drawing gets the same box, ratio and caption as a photo. Slotted media carries its own text alternative: an `alt` on the img (empty when it conveys nothing), or `role="img"` with an `aria-label`, `aria-labelledby` or `<title>` on the svg.
|
|
12657
12659
|
* The internal `error` listener is attached only to the built-in `<img>`, so
|
|
12658
12660
|
* slotted content does not trigger the error-state overlay automatically.
|
|
12659
12661
|
* Consumers slotting their own image are responsible for handling its
|
|
@@ -12686,6 +12688,9 @@ var NLDDImage = (_NLDDImage = class NLDDImage extends LitElement {
|
|
|
12686
12688
|
this._imageErrored = false;
|
|
12687
12689
|
this._warnedWidth = false;
|
|
12688
12690
|
this._warnedAlt = false;
|
|
12691
|
+
this._onMediaSlotChange = () => {
|
|
12692
|
+
this._warnMissingAlt();
|
|
12693
|
+
};
|
|
12689
12694
|
this._onImageLoad = () => {
|
|
12690
12695
|
this._imageLoaded = true;
|
|
12691
12696
|
this._imageErrored = false;
|
|
@@ -12762,9 +12767,31 @@ var NLDDImage = (_NLDDImage = class NLDDImage extends LitElement {
|
|
|
12762
12767
|
if (n !== void 0) this.style.setProperty("--_max-width", `${n}px`);
|
|
12763
12768
|
else this.style.removeProperty("--_max-width");
|
|
12764
12769
|
}
|
|
12770
|
+
this._warnMissingAlt();
|
|
12765
12771
|
this.toggleAttribute("loaded", this._imageLoaded);
|
|
12766
12772
|
this.toggleAttribute("errored", this._imageErrored);
|
|
12767
12773
|
}
|
|
12774
|
+
/**
|
|
12775
|
+
* Says in DEV when an image has no text alternative. A non-decorative image
|
|
12776
|
+
* without one is a silent a11y failure (WCAG H37), so this surfaces it
|
|
12777
|
+
* during build and Storybook and stays quiet in production.
|
|
12778
|
+
*
|
|
12779
|
+
* Slotted media replaces the built-in `<img>` and carries its own
|
|
12780
|
+
* alternative, so once something is slotted, that is what gets judged.
|
|
12781
|
+
* With neither slotted media nor a `src` or `srcset` there is no image yet:
|
|
12782
|
+
* a source or media that arrives later is judged when it does. The latch
|
|
12783
|
+
* resets once the problem is solved, so bringing it back warns again.
|
|
12784
|
+
*/
|
|
12785
|
+
_warnMissingAlt() {}
|
|
12786
|
+
/**
|
|
12787
|
+
* What the consumer put in the default slot, followed through a forwarded
|
|
12788
|
+
* slot. The top level is deliberately not flattened: with nothing assigned,
|
|
12789
|
+
* flattening returns the built-in fallback `<img>`, which always has an
|
|
12790
|
+
* `alt` attribute and would pass every time.
|
|
12791
|
+
*/
|
|
12792
|
+
_slottedMedia() {
|
|
12793
|
+
return ((this.shadowRoot?.querySelector("slot:not([name])"))?.assignedElements() ?? []).flatMap((el) => el instanceof HTMLSlotElement ? el.assignedElements({ flatten: true }) : [el]);
|
|
12794
|
+
}
|
|
12768
12795
|
firstUpdated() {
|
|
12769
12796
|
const img = this.shadowRoot?.querySelector(".image__img");
|
|
12770
12797
|
if (img?.complete && img.naturalWidth > 0) this._imageLoaded = true;
|
|
@@ -16806,11 +16833,11 @@ var cursorLineBoundaryRight = (view) => moveSel(view, (range) => moveByLineBound
|
|
|
16806
16833
|
/**
|
|
16807
16834
|
Move the selection to the start of the line.
|
|
16808
16835
|
*/
|
|
16809
|
-
var cursorLineStart = (view) => moveSel(view, (range) =>
|
|
16836
|
+
var cursorLineStart = (view) => moveSel(view, (range) => view.moveToLineBoundary(range, false, false));
|
|
16810
16837
|
/**
|
|
16811
16838
|
Move the selection to the end of the line.
|
|
16812
16839
|
*/
|
|
16813
|
-
var cursorLineEnd = (view) => moveSel(view, (range) =>
|
|
16840
|
+
var cursorLineEnd = (view) => moveSel(view, (range) => view.moveToLineBoundary(range, true, false));
|
|
16814
16841
|
function toMatchingBracket(state, dispatch, extend) {
|
|
16815
16842
|
let found = false, selection = updateSel(state.selection, (range) => {
|
|
16816
16843
|
let matching = matchBrackets(state, range.head, -1) || matchBrackets(state, range.head, 1) || range.head > 0 && matchBrackets(state, range.head - 1, 1) || range.head < state.doc.length && matchBrackets(state, range.head + 1, -1);
|
|
@@ -23821,12 +23848,12 @@ var radioButtonStyles = css`
|
|
|
23821
23848
|
|
|
23822
23849
|
/* # Elements */
|
|
23823
23850
|
|
|
23824
|
-
.radio-
|
|
23851
|
+
.radio-button__validation-input {
|
|
23825
23852
|
position: absolute;
|
|
23826
23853
|
inset: 0;
|
|
23827
23854
|
opacity: 0;
|
|
23828
|
-
z-index: 1;
|
|
23829
23855
|
margin: 0;
|
|
23856
|
+
pointer-events: none;
|
|
23830
23857
|
width: 100%;
|
|
23831
23858
|
height: 100%;
|
|
23832
23859
|
}
|
|
@@ -23853,61 +23880,55 @@ var radioButtonStyles = css`
|
|
|
23853
23880
|
transform: translate(-50%, -50%) scale(0);
|
|
23854
23881
|
}
|
|
23855
23882
|
|
|
23856
|
-
|
|
23883
|
+
:host([checked]) .radio-button__outer-shape {
|
|
23857
23884
|
border-color: var(--components-radio-button-is-selected-border-color);
|
|
23858
23885
|
background-color: var(--components-radio-button-is-selected-background-color);
|
|
23859
23886
|
}
|
|
23860
23887
|
|
|
23861
|
-
|
|
23862
|
-
transform: translate(-50%, -50%) scale(1);
|
|
23863
|
-
}
|
|
23864
|
-
|
|
23865
|
-
/* Decorative has no input to hang :checked on, so the state comes from the
|
|
23866
|
-
host attribute instead. */
|
|
23867
|
-
:host([decorative][checked]) .radio-button__outer-shape {
|
|
23868
|
-
border-color: var(--components-radio-button-is-selected-border-color);
|
|
23869
|
-
background-color: var(--components-radio-button-is-selected-background-color);
|
|
23870
|
-
}
|
|
23871
|
-
|
|
23872
|
-
:host([decorative][checked]) .radio-button__inner-shape {
|
|
23888
|
+
:host([checked]) .radio-button__inner-shape {
|
|
23873
23889
|
transform: translate(-50%, -50%) scale(1);
|
|
23874
23890
|
}
|
|
23875
23891
|
|
|
23876
23892
|
@media (hover: hover) {
|
|
23877
|
-
|
|
23893
|
+
:host(:hover:not([disabled])) .radio-button__outer-shape {
|
|
23878
23894
|
border-color: var(--components-radio-button-is-hovered-border-color);
|
|
23879
23895
|
}
|
|
23880
23896
|
|
|
23881
|
-
|
|
23897
|
+
:host([checked]:hover:not([disabled])) .radio-button__outer-shape {
|
|
23882
23898
|
border-color: var(--components-radio-button-is-selected-is-hovered-border-color);
|
|
23883
23899
|
background-color: var(--components-radio-button-is-selected-is-hovered-background-color);
|
|
23884
23900
|
}
|
|
23885
23901
|
|
|
23886
|
-
|
|
23902
|
+
:host([checked]:hover:not([disabled])) .radio-button__inner-shape {
|
|
23887
23903
|
border-color: var(--components-radio-button-is-selected-is-hovered-inner-shape-border-color);
|
|
23888
23904
|
}
|
|
23889
23905
|
}
|
|
23890
23906
|
|
|
23891
|
-
|
|
23907
|
+
:host(:active:not([disabled])) .radio-button__outer-shape {
|
|
23892
23908
|
border-color: var(--components-radio-button-is-active-border-color);
|
|
23893
23909
|
}
|
|
23894
23910
|
|
|
23895
|
-
|
|
23911
|
+
:host([checked]:active:not([disabled])) .radio-button__outer-shape {
|
|
23896
23912
|
border-color: var(--components-radio-button-is-selected-is-active-border-color);
|
|
23897
23913
|
background-color: var(--components-radio-button-is-selected-is-active-background-color);
|
|
23898
23914
|
}
|
|
23899
23915
|
|
|
23900
|
-
|
|
23916
|
+
:host([checked]:active:not([disabled])) .radio-button__inner-shape {
|
|
23901
23917
|
border-color: var(--components-radio-button-is-selected-is-active-inner-shape-border-color);
|
|
23902
23918
|
}
|
|
23903
23919
|
|
|
23904
|
-
|
|
23920
|
+
:host(:focus-visible) {
|
|
23921
|
+
outline: none;
|
|
23922
|
+
}
|
|
23923
|
+
|
|
23924
|
+
:host(:focus-visible) .radio-button__outer-shape,
|
|
23925
|
+
:host([focus-ring]) .radio-button__outer-shape {
|
|
23905
23926
|
outline: var(--semantics-focus-ring-outline);
|
|
23906
23927
|
outline-offset: var(--semantics-focus-ring-outline-offset);
|
|
23907
23928
|
box-shadow: var(--semantics-focus-ring-box-shadow);
|
|
23908
23929
|
}
|
|
23909
23930
|
|
|
23910
|
-
|
|
23931
|
+
:host([disabled]) .radio-button__outer-shape {
|
|
23911
23932
|
opacity: var(--primitives-opacity-disabled);
|
|
23912
23933
|
}
|
|
23913
23934
|
`;
|
|
@@ -23920,16 +23941,14 @@ function radioButtonTemplate(component) {
|
|
|
23920
23941
|
</div>
|
|
23921
23942
|
`;
|
|
23922
23943
|
return html`
|
|
23923
|
-
<input class="radio-
|
|
23944
|
+
<input class="radio-button__validation-input"
|
|
23924
23945
|
type="radio"
|
|
23925
|
-
|
|
23926
|
-
?disabled=${component.disabled}
|
|
23946
|
+
name="nldd-validation"
|
|
23927
23947
|
?required=${component.required}
|
|
23928
|
-
|
|
23929
|
-
|
|
23930
|
-
|
|
23931
|
-
|
|
23932
|
-
@change=${component._handleChange}
|
|
23948
|
+
?disabled=${component.disabled}
|
|
23949
|
+
.checked=${component._groupHasSelection}
|
|
23950
|
+
tabindex="-1"
|
|
23951
|
+
aria-hidden="true"
|
|
23933
23952
|
>
|
|
23934
23953
|
<div class="radio-button__outer-shape"
|
|
23935
23954
|
aria-hidden="true"
|
|
@@ -23939,32 +23958,167 @@ function radioButtonTemplate(component) {
|
|
|
23939
23958
|
`;
|
|
23940
23959
|
}
|
|
23941
23960
|
//#endregion
|
|
23961
|
+
//#region src/utilities/radio-position.ts
|
|
23962
|
+
/** A position for every radio of a group, in the order given. */
|
|
23963
|
+
function radioPositions(radios, isChecked, isDisabled) {
|
|
23964
|
+
const enabled = radios.filter((radio) => !isDisabled(radio));
|
|
23965
|
+
const stop = enabled.find(isChecked) ?? enabled[0];
|
|
23966
|
+
return radios.map((radio, index) => ({
|
|
23967
|
+
posInSet: index + 1,
|
|
23968
|
+
setSize: radios.length,
|
|
23969
|
+
tabbable: radio === stop
|
|
23970
|
+
}));
|
|
23971
|
+
}
|
|
23972
|
+
//#endregion
|
|
23973
|
+
//#region src/utilities/radio-grouping.ts
|
|
23974
|
+
/**
|
|
23975
|
+
* Both tags in one list, because the platform groups on the name and not on the
|
|
23976
|
+
* element. A button and a field that share a name are one group to the browser,
|
|
23977
|
+
* and two lists would have them counting different sets.
|
|
23978
|
+
*/
|
|
23979
|
+
var RADIO_TAGS = "nldd-radio-button, nldd-radio-button-field";
|
|
23980
|
+
var ARROW_STEPS = {
|
|
23981
|
+
ArrowDown: 1,
|
|
23982
|
+
ArrowRight: 1,
|
|
23983
|
+
ArrowUp: -1,
|
|
23984
|
+
ArrowLeft: -1
|
|
23985
|
+
};
|
|
23986
|
+
/** The controller of each radio, so a recount can reach its siblings. */
|
|
23987
|
+
var groupings = /* @__PURE__ */ new WeakMap();
|
|
23988
|
+
/** A radio that draws the shape for something else owns no state of its own. */
|
|
23989
|
+
function isMember(radio) {
|
|
23990
|
+
return !radio.decorative;
|
|
23991
|
+
}
|
|
23992
|
+
var RadioGrouping = class RadioGrouping {
|
|
23993
|
+
constructor(host) {
|
|
23994
|
+
this.host = host;
|
|
23995
|
+
this.position = null;
|
|
23996
|
+
this.answered = false;
|
|
23997
|
+
this._groupedIn = null;
|
|
23998
|
+
groupings.set(host, this);
|
|
23999
|
+
host.addController(this);
|
|
24000
|
+
}
|
|
24001
|
+
hostConnected() {
|
|
24002
|
+
queueMicrotask(() => this.regroup());
|
|
24003
|
+
}
|
|
24004
|
+
hostDisconnected() {
|
|
24005
|
+
this.regroup();
|
|
24006
|
+
}
|
|
24007
|
+
/** Reads which group this radio is in now, and recounts the one it left. */
|
|
24008
|
+
regroup() {
|
|
24009
|
+
const was = this._groupedIn;
|
|
24010
|
+
const now = this._key();
|
|
24011
|
+
this._groupedIn = now;
|
|
24012
|
+
const moved = !now || !was || was.root !== now.root || was.name !== now.name || was.form !== now.form;
|
|
24013
|
+
if (was && moved) RadioGrouping._recount(was);
|
|
24014
|
+
if (now) RadioGrouping._recount(now);
|
|
24015
|
+
else this._apply(null, this.host.checked);
|
|
24016
|
+
}
|
|
24017
|
+
/** Counts the group again, after this radio was checked or disabled. */
|
|
24018
|
+
refresh() {
|
|
24019
|
+
if (this._groupedIn) RadioGrouping._recount(this._groupedIn);
|
|
24020
|
+
else this._apply(null, this.host.checked);
|
|
24021
|
+
}
|
|
24022
|
+
/** Unchecks the others, as checking a native radio does. */
|
|
24023
|
+
uncheckOthers() {
|
|
24024
|
+
const key = this._key();
|
|
24025
|
+
if (!key) return;
|
|
24026
|
+
for (const radio of RadioGrouping._radiosIn(key)) {
|
|
24027
|
+
if (radio === this.host || !radio.checked) continue;
|
|
24028
|
+
radio.checked = false;
|
|
24029
|
+
radio.commitFormValue();
|
|
24030
|
+
}
|
|
24031
|
+
}
|
|
24032
|
+
/**
|
|
24033
|
+
* Moves to the next radio of the group and checks it, as the arrow keys do
|
|
24034
|
+
* for native radios. Disabled options are stepped over, and the ends wrap.
|
|
24035
|
+
*
|
|
24036
|
+
* @returns whether the key was handled.
|
|
24037
|
+
*/
|
|
24038
|
+
moveWithArrow(e) {
|
|
24039
|
+
const step = ARROW_STEPS[e.key];
|
|
24040
|
+
const key = step ? this._key() : null;
|
|
24041
|
+
if (!key || !this.position) return false;
|
|
24042
|
+
const radios = RadioGrouping._radiosIn(key).filter((radio) => !radio.disabled);
|
|
24043
|
+
const at = radios.indexOf(this.host);
|
|
24044
|
+
if (at === -1) return false;
|
|
24045
|
+
e.preventDefault();
|
|
24046
|
+
const next = radios[(at + step + radios.length) % radios.length];
|
|
24047
|
+
next.focus();
|
|
24048
|
+
next.select();
|
|
24049
|
+
return true;
|
|
24050
|
+
}
|
|
24051
|
+
_key() {
|
|
24052
|
+
if (!this.host.isConnected || !isMember(this.host) || !this.host.name) return null;
|
|
24053
|
+
return {
|
|
24054
|
+
root: this.host.getRootNode(),
|
|
24055
|
+
name: this.host.name,
|
|
24056
|
+
form: this.host.internals.form
|
|
24057
|
+
};
|
|
24058
|
+
}
|
|
24059
|
+
/** Compared by value: a recount hands out new objects for the same place, and
|
|
24060
|
+
* asking for a render on every one of those is a render per radio per
|
|
24061
|
+
* keystroke, each of them scheduled from inside an update that had just
|
|
24062
|
+
* finished. */
|
|
24063
|
+
_apply(position, answered) {
|
|
24064
|
+
if (this.answered === answered && this.position?.posInSet === position?.posInSet && this.position?.setSize === position?.setSize && this.position?.tabbable === position?.tabbable) return;
|
|
24065
|
+
this.position = position;
|
|
24066
|
+
this.answered = answered;
|
|
24067
|
+
this.host.requestUpdate();
|
|
24068
|
+
}
|
|
24069
|
+
static _radiosIn({ root, name, form }) {
|
|
24070
|
+
const radios = root.querySelectorAll?.(RADIO_TAGS) ?? [];
|
|
24071
|
+
return Array.from(radios).filter((radio) => groupings.has(radio) && radio.isConnected && radio.name === name && isMember(radio) && radio.internals.form === form);
|
|
24072
|
+
}
|
|
24073
|
+
/** A radio on its own keeps the place the browser gives it: none. */
|
|
24074
|
+
static _recount(key) {
|
|
24075
|
+
const radios = RadioGrouping._radiosIn(key);
|
|
24076
|
+
const positions = radios.length > 1 ? radioPositions(radios, (radio) => radio.checked, (radio) => radio.disabled) : [];
|
|
24077
|
+
const answered = radios.some((radio) => radio.checked);
|
|
24078
|
+
radios.forEach((radio, index) => {
|
|
24079
|
+
groupings.get(radio)?._apply(positions[index] ?? null, answered);
|
|
24080
|
+
});
|
|
24081
|
+
}
|
|
24082
|
+
};
|
|
24083
|
+
//#endregion
|
|
23942
24084
|
//#region src/components/inputs/radio-button/radio-button.ts
|
|
23943
24085
|
/**
|
|
23944
24086
|
* Nederlandse Digitale Dienst Radio Button Component (Lit + TypeScript)
|
|
23945
24087
|
*
|
|
23946
|
-
* WAI-ARIA:
|
|
23947
|
-
* role="radiogroup" and
|
|
24088
|
+
* WAI-ARIA: put the buttons of one group in a container with
|
|
24089
|
+
* role="radiogroup" and a name of its own. A screen reader counts the options
|
|
24090
|
+
* from that container, so a bare <fieldset> (which is a group, not a
|
|
24091
|
+
* radiogroup) leaves them uncounted. nldd-radio-button-group does this for you.
|
|
24092
|
+
*
|
|
24093
|
+
* Radio buttons with the same name, in the same form and the same tree, form
|
|
24094
|
+
* one group the way native radios do: checking one unchecks the others, the
|
|
24095
|
+
* arrow keys move between them, Tab stops at the group once, and a screen
|
|
24096
|
+
* reader hears each one's place in it. The element itself is the radio, because
|
|
24097
|
+
* an input per shadow root would leave every button a group of one.
|
|
24098
|
+
*
|
|
24099
|
+
* The shape is all this component draws. A radio with a label beside it is
|
|
24100
|
+
* nldd-radio-button-field.
|
|
23948
24101
|
*
|
|
23949
24102
|
* @example
|
|
23950
|
-
* <fieldset>
|
|
23951
|
-
* <legend>Kies een optie</legend>
|
|
23952
|
-
* <nldd-radio-button name="options" value="1"
|
|
23953
|
-
* <nldd-radio-button name="options" value="2"
|
|
24103
|
+
* <fieldset role="radiogroup" aria-labelledby="options-label">
|
|
24104
|
+
* <legend id="options-label">Kies een optie</legend>
|
|
24105
|
+
* <nldd-radio-button name="options" value="1" accessible-label="Optie 1"></nldd-radio-button>
|
|
24106
|
+
* <nldd-radio-button name="options" value="2" accessible-label="Optie 2"></nldd-radio-button>
|
|
23954
24107
|
* </fieldset>
|
|
23955
24108
|
*
|
|
23956
24109
|
* @element nldd-radio-button
|
|
23957
24110
|
* @attr {boolean} checked - Checked state
|
|
23958
24111
|
* @attr {boolean} disabled - Disabled state
|
|
23959
24112
|
* @attr {boolean} no-tab - Takes the control out of the tab order (tabindex="-1"), for a control owned by a roving container (a row of an nldd-list, where the arrow keys move between rows) that manages focus itself. Still mouse- and script-focusable.
|
|
23960
|
-
* @attr {boolean} decorative - Renders the shape
|
|
24113
|
+
* @attr {boolean} decorative - Renders the shape only: no role, no focus, no
|
|
23961
24114
|
* name/value, nothing announced. For a control that owns the state elsewhere,
|
|
23962
|
-
* such as a list row that is itself the radio;
|
|
23963
|
-
* would nest a control inside a control.
|
|
23964
|
-
* @attr {boolean} required - Required state
|
|
24115
|
+
* such as a list row or an nldd-radio-button-field that is itself the radio;
|
|
24116
|
+
* putting a second radio in there would nest a control inside a control.
|
|
24117
|
+
* @attr {boolean} required - Required state. What it asks is whether anything in the group is checked, as a native radio does.
|
|
24118
|
+
* @attr {boolean} focus-ring - Draws the focus ring around the shape. Set by nldd-radio-button-field, which is the radio itself and holds the focus. Not part of the public API.
|
|
23965
24119
|
* @attr {string} name - Radio group name for form submission; ties the buttons of one group together
|
|
23966
24120
|
* @attr {string} value - Value submitted with the form when this radio button is checked
|
|
23967
|
-
* @attr {string} accessible-label - Accessible label
|
|
24121
|
+
* @attr {string} accessible-label - Accessible label, set as aria-label on this element: it is the radio.
|
|
23968
24122
|
* @attr {boolean} invalid - Marks the control as invalid. Announced with aria-invalid; nothing is drawn for it.
|
|
23969
24123
|
* Note: aria-labelledby is not supported as IDREF resolution cannot cross shadow DOM boundaries.
|
|
23970
24124
|
*
|
|
@@ -23980,14 +24134,102 @@ var NLDDRadioButton = (_NLDDRadioButton = class NLDDRadioButton extends Describe
|
|
|
23980
24134
|
this.disabled = false;
|
|
23981
24135
|
this.noTab = false;
|
|
23982
24136
|
this.required = false;
|
|
24137
|
+
this.focusRing = false;
|
|
23983
24138
|
this.name = "";
|
|
23984
24139
|
this.value = "";
|
|
23985
24140
|
this.accessibleLabel = "";
|
|
23986
24141
|
this.invalid = false;
|
|
24142
|
+
this._grouping = new RadioGrouping(this);
|
|
24143
|
+
this._appliedLabel = null;
|
|
24144
|
+
this._onClick = () => {
|
|
24145
|
+
this.select();
|
|
24146
|
+
};
|
|
24147
|
+
this._onKeyDown = (e) => {
|
|
24148
|
+
if (this.disabled || this.decorative) return;
|
|
24149
|
+
if (e.key === " ") {
|
|
24150
|
+
e.preventDefault();
|
|
24151
|
+
this.select();
|
|
24152
|
+
return;
|
|
24153
|
+
}
|
|
24154
|
+
if (e.key === "Enter") {
|
|
24155
|
+
submitOnEnter(this, e);
|
|
24156
|
+
return;
|
|
24157
|
+
}
|
|
24158
|
+
if (!this.noTab) this._grouping.moveWithArrow(e);
|
|
24159
|
+
};
|
|
24160
|
+
}
|
|
24161
|
+
/** Whether anything in its group is checked, which is what `required` asks
|
|
24162
|
+
* of a radio: one of these, not this one. */
|
|
24163
|
+
get _groupHasSelection() {
|
|
24164
|
+
return this._grouping.answered;
|
|
24165
|
+
}
|
|
24166
|
+
connectedCallback() {
|
|
24167
|
+
super.connectedCallback();
|
|
24168
|
+
this.addEventListener("click", this._onClick);
|
|
24169
|
+
this.addEventListener("keydown", this._onKeyDown);
|
|
24170
|
+
}
|
|
24171
|
+
disconnectedCallback() {
|
|
24172
|
+
super.disconnectedCallback();
|
|
24173
|
+
this.removeEventListener("click", this._onClick);
|
|
24174
|
+
this.removeEventListener("keydown", this._onKeyDown);
|
|
23987
24175
|
}
|
|
23988
24176
|
firstUpdated() {
|
|
23989
24177
|
this._initialChecked = this.checked;
|
|
23990
24178
|
}
|
|
24179
|
+
/** Counting happens before the render, not after it: the place this radio is
|
|
24180
|
+
* given decides what `updated()` writes on it, and a count from there would
|
|
24181
|
+
* ask for a second render of what was just drawn. */
|
|
24182
|
+
willUpdate(changed) {
|
|
24183
|
+
if (changed.has("checked") && this.checked) this._grouping.uncheckOthers();
|
|
24184
|
+
if (changed.has("name") || changed.has("decorative")) this._grouping.regroup();
|
|
24185
|
+
else if (changed.has("checked") || changed.has("disabled")) this._grouping.refresh();
|
|
24186
|
+
}
|
|
24187
|
+
updated(changed) {
|
|
24188
|
+
super.updated(changed);
|
|
24189
|
+
this._syncHostAria();
|
|
24190
|
+
}
|
|
24191
|
+
/**
|
|
24192
|
+
* This element is the radio.
|
|
24193
|
+
*
|
|
24194
|
+
* A native `<input type="radio">` groups with the radios of the same name in
|
|
24195
|
+
* its own tree, and one per shadow root leaves every radio a group of one: a
|
|
24196
|
+
* screen reader announces "1 of 1" and Tab stops at each of them. The role
|
|
24197
|
+
* on the element itself puts every radio of a group in one tree again, where
|
|
24198
|
+
* the browser counts them together.
|
|
24199
|
+
*/
|
|
24200
|
+
_syncHostAria() {
|
|
24201
|
+
if (this.decorative) {
|
|
24202
|
+
for (const name of [
|
|
24203
|
+
"role",
|
|
24204
|
+
"aria-checked",
|
|
24205
|
+
"aria-disabled",
|
|
24206
|
+
"aria-posinset",
|
|
24207
|
+
"aria-setsize",
|
|
24208
|
+
"tabindex"
|
|
24209
|
+
]) this.removeAttribute(name);
|
|
24210
|
+
return;
|
|
24211
|
+
}
|
|
24212
|
+
this.setAttribute("role", "radio");
|
|
24213
|
+
this.setAttribute("aria-checked", String(this.checked));
|
|
24214
|
+
this._appliedLabel = setOwnedAttribute(this, "aria-label", this.accessibleLabel, this._appliedLabel);
|
|
24215
|
+
if (this.disabled) this.setAttribute("aria-disabled", "true");
|
|
24216
|
+
else this.removeAttribute("aria-disabled");
|
|
24217
|
+
const position = this._grouping.position;
|
|
24218
|
+
if (position) {
|
|
24219
|
+
this.setAttribute("aria-posinset", String(position.posInSet));
|
|
24220
|
+
this.setAttribute("aria-setsize", String(position.setSize));
|
|
24221
|
+
} else {
|
|
24222
|
+
this.removeAttribute("aria-posinset");
|
|
24223
|
+
this.removeAttribute("aria-setsize");
|
|
24224
|
+
}
|
|
24225
|
+
const tabbable = !this.disabled && !this.noTab && position?.tabbable !== false;
|
|
24226
|
+
this.setAttribute("tabindex", tabbable ? "0" : "-1");
|
|
24227
|
+
}
|
|
24228
|
+
/** The element assistive software meets is this one, so the description and
|
|
24229
|
+
* the invalid state belong here rather than on something inside. */
|
|
24230
|
+
describedTarget() {
|
|
24231
|
+
return this;
|
|
24232
|
+
}
|
|
23991
24233
|
formValue() {
|
|
23992
24234
|
return this.checked ? this.value : null;
|
|
23993
24235
|
}
|
|
@@ -23997,20 +24239,15 @@ var NLDDRadioButton = (_NLDDRadioButton = class NLDDRadioButton extends Describe
|
|
|
23997
24239
|
formStateRestoreCallback(state) {
|
|
23998
24240
|
this.checked = state !== null;
|
|
23999
24241
|
}
|
|
24242
|
+
/** Checks this radio, the way a click or the space bar does. */
|
|
24000
24243
|
select() {
|
|
24001
|
-
if (this.disabled || this.checked) return;
|
|
24002
|
-
|
|
24003
|
-
if (!input) return;
|
|
24004
|
-
input.checked = true;
|
|
24005
|
-
input.dispatchEvent(new Event("change", { bubbles: true }));
|
|
24006
|
-
}
|
|
24007
|
-
_handleChange(e) {
|
|
24008
|
-
const input = e.target;
|
|
24009
|
-
this.checked = input.checked;
|
|
24244
|
+
if (this.disabled || this.decorative || this.checked) return;
|
|
24245
|
+
this.checked = true;
|
|
24010
24246
|
this.commitFormValue();
|
|
24247
|
+
this._grouping.uncheckOthers();
|
|
24011
24248
|
this.dispatchEvent(new CustomEvent("change", {
|
|
24012
24249
|
detail: {
|
|
24013
|
-
checked:
|
|
24250
|
+
checked: true,
|
|
24014
24251
|
value: this.value,
|
|
24015
24252
|
name: this.name
|
|
24016
24253
|
},
|
|
@@ -24018,13 +24255,6 @@ var NLDDRadioButton = (_NLDDRadioButton = class NLDDRadioButton extends Describe
|
|
|
24018
24255
|
composed: true
|
|
24019
24256
|
}));
|
|
24020
24257
|
}
|
|
24021
|
-
/**
|
|
24022
|
-
* Delegates focus to the inner native radio `<input>`, so consumers can
|
|
24023
|
-
* call `radioEl.focus()` without reaching into shadow DOM.
|
|
24024
|
-
*/
|
|
24025
|
-
focus(options) {
|
|
24026
|
-
this.shadowRoot?.querySelector(".radio-button__input")?.focus(options);
|
|
24027
|
-
}
|
|
24028
24258
|
render() {
|
|
24029
24259
|
return radioButtonTemplate(this);
|
|
24030
24260
|
}
|
|
@@ -24050,6 +24280,11 @@ __decorate([property({
|
|
|
24050
24280
|
type: Boolean,
|
|
24051
24281
|
reflect: true
|
|
24052
24282
|
})], NLDDRadioButton.prototype, "required", void 0);
|
|
24283
|
+
__decorate([property({
|
|
24284
|
+
type: Boolean,
|
|
24285
|
+
reflect: true,
|
|
24286
|
+
attribute: "focus-ring"
|
|
24287
|
+
})], NLDDRadioButton.prototype, "focusRing", void 0);
|
|
24053
24288
|
__decorate([property({
|
|
24054
24289
|
reflect: true,
|
|
24055
24290
|
converter: reflectNonDefault("")
|
|
@@ -24077,12 +24312,29 @@ var radioButtonFieldStyles = css`
|
|
|
24077
24312
|
:host {
|
|
24078
24313
|
${inheritedTextReset}
|
|
24079
24314
|
display: block;
|
|
24315
|
+
position: relative;
|
|
24316
|
+
cursor: default;
|
|
24317
|
+
-webkit-tap-highlight-color: transparent;
|
|
24080
24318
|
}
|
|
24081
24319
|
|
|
24082
24320
|
:host([hidden]) {
|
|
24083
24321
|
display: none;
|
|
24084
24322
|
}
|
|
24085
24323
|
|
|
24324
|
+
:host(:focus-visible) {
|
|
24325
|
+
outline: none;
|
|
24326
|
+
}
|
|
24327
|
+
|
|
24328
|
+
.radio-button-field__validation-input {
|
|
24329
|
+
position: absolute;
|
|
24330
|
+
inset: 0;
|
|
24331
|
+
opacity: 0;
|
|
24332
|
+
margin: 0;
|
|
24333
|
+
pointer-events: none;
|
|
24334
|
+
width: 100%;
|
|
24335
|
+
height: 100%;
|
|
24336
|
+
}
|
|
24337
|
+
|
|
24086
24338
|
|
|
24087
24339
|
/* # Block */
|
|
24088
24340
|
|
|
@@ -24121,22 +24373,27 @@ var radioButtonFieldStyles = css`
|
|
|
24121
24373
|
//#region src/components/inputs/radio-button-field/radio-button-field.template.ts
|
|
24122
24374
|
function radioButtonFieldTemplate(component) {
|
|
24123
24375
|
return html`
|
|
24124
|
-
<div class="radio-button-field"
|
|
24125
|
-
|
|
24126
|
-
|
|
24376
|
+
<div class="radio-button-field">
|
|
24377
|
+
<input class="radio-button-field__validation-input"
|
|
24378
|
+
type="radio"
|
|
24379
|
+
name="nldd-validation"
|
|
24380
|
+
?required=${component.required}
|
|
24381
|
+
?disabled=${component.disabled}
|
|
24382
|
+
.checked=${component._answered}
|
|
24383
|
+
tabindex="-1"
|
|
24384
|
+
aria-hidden="true"
|
|
24385
|
+
>
|
|
24127
24386
|
<div class="radio-button-field__control">
|
|
24128
24387
|
<nldd-radio-button
|
|
24129
|
-
|
|
24388
|
+
decorative
|
|
24130
24389
|
?checked=${component.checked}
|
|
24131
24390
|
?disabled=${component.disabled}
|
|
24132
|
-
?
|
|
24133
|
-
name=${component.name || ""}
|
|
24134
|
-
value=${component.value}
|
|
24135
|
-
accessible-label=${component.label || nothing}
|
|
24136
|
-
@change=${component._handleChange}
|
|
24391
|
+
?focus-ring=${component._focusRing}
|
|
24137
24392
|
></nldd-radio-button>
|
|
24138
24393
|
</div>
|
|
24139
|
-
<span class="radio-button-field__label"
|
|
24394
|
+
<span class="radio-button-field__label">
|
|
24395
|
+
${component.label}
|
|
24396
|
+
</span>
|
|
24140
24397
|
</div>
|
|
24141
24398
|
`;
|
|
24142
24399
|
}
|
|
@@ -24145,20 +24402,29 @@ function radioButtonFieldTemplate(component) {
|
|
|
24145
24402
|
/**
|
|
24146
24403
|
* Nederlandse Digitale Dienst Radio Button Field Component (Lit + TypeScript)
|
|
24147
24404
|
*
|
|
24148
|
-
* A radio button with an inline label. Use inside nldd-radio-button-group
|
|
24149
|
-
*
|
|
24405
|
+
* A radio button with an inline label. Use inside nldd-radio-button-group,
|
|
24406
|
+
* which sets the name and labels the group.
|
|
24407
|
+
*
|
|
24408
|
+
* Fields that stand on their own group by name, the way native radios do:
|
|
24409
|
+
* checking one unchecks the others, the arrow keys move between them and Tab
|
|
24410
|
+
* stops at the group once. Put them in a container with role="radiogroup", or
|
|
24411
|
+
* a screen reader has nothing to count them from.
|
|
24412
|
+
*
|
|
24413
|
+
* The field is the radio: it carries the role, the state, the label it is
|
|
24414
|
+
* announced by and the focus, and the nldd-radio-button inside only draws the
|
|
24415
|
+
* shape. A radio of its own in there would be a second control inside a
|
|
24416
|
+
* control, and a group of one to count.
|
|
24150
24417
|
*
|
|
24151
|
-
* Form-associated: the checked field submits its `value` under `name
|
|
24152
|
-
*
|
|
24153
|
-
*
|
|
24154
|
-
* the group's job, so exactly one value per group reaches the form.
|
|
24418
|
+
* Form-associated: the checked field submits its `value` under `name`.
|
|
24419
|
+
* Unchecking siblings is the group's job, so exactly one value per group
|
|
24420
|
+
* reaches the form.
|
|
24155
24421
|
*
|
|
24156
24422
|
* @element nldd-radio-button-field
|
|
24157
24423
|
* @attr {boolean} checked - Checked state
|
|
24158
24424
|
* @attr {boolean} disabled - Disabled state
|
|
24159
24425
|
* @attr {string} value - Value for form submission
|
|
24160
|
-
* @attr {string} name - Radio group name for form submission
|
|
24161
|
-
* @attr {boolean} required - Required state
|
|
24426
|
+
* @attr {string} name - Radio group name for form submission; ties the fields of one group together. Set automatically by nldd-radio-button-group.
|
|
24427
|
+
* @attr {boolean} required - Required state. What it asks is whether anything in the group is checked, as a native radio does. Set automatically by nldd-radio-button-group.
|
|
24162
24428
|
* @attr {string} label - Label text for the radio button
|
|
24163
24429
|
* @attr {boolean} invalid - Marks the control as invalid. Announced with aria-invalid; nothing is drawn for it.
|
|
24164
24430
|
*
|
|
@@ -24176,10 +24442,98 @@ var NLDDRadioButtonField = (_NLDDRadioButtonField = class NLDDRadioButtonField e
|
|
|
24176
24442
|
this.required = false;
|
|
24177
24443
|
this.label = "";
|
|
24178
24444
|
this.invalid = false;
|
|
24445
|
+
this._groupPosition = null;
|
|
24446
|
+
this._groupHasSelection = false;
|
|
24447
|
+
this._grouping = new RadioGrouping(this);
|
|
24448
|
+
this._appliedLabel = null;
|
|
24449
|
+
this._focusRing = false;
|
|
24450
|
+
this._onClick = () => {
|
|
24451
|
+
this.select();
|
|
24452
|
+
};
|
|
24453
|
+
this._onFocus = () => {
|
|
24454
|
+
this._focusRing = isKeyboardMode();
|
|
24455
|
+
};
|
|
24456
|
+
this._onBlur = () => {
|
|
24457
|
+
this._focusRing = false;
|
|
24458
|
+
};
|
|
24459
|
+
this._onKeyDown = (e) => {
|
|
24460
|
+
if (this.disabled) return;
|
|
24461
|
+
if (e.key === " ") {
|
|
24462
|
+
e.preventDefault();
|
|
24463
|
+
this.select();
|
|
24464
|
+
return;
|
|
24465
|
+
}
|
|
24466
|
+
if (e.key === "Enter") {
|
|
24467
|
+
submitOnEnter(this, e);
|
|
24468
|
+
return;
|
|
24469
|
+
}
|
|
24470
|
+
if (!this._groupPosition) this._grouping.moveWithArrow(e);
|
|
24471
|
+
};
|
|
24472
|
+
}
|
|
24473
|
+
/** The place it is announced with: from the group around it, or from the
|
|
24474
|
+
* radios it shares a name with. */
|
|
24475
|
+
get _position() {
|
|
24476
|
+
return this._groupPosition ?? this._grouping.position;
|
|
24477
|
+
}
|
|
24478
|
+
/** Whether anything in its group is checked. Either source saying so is
|
|
24479
|
+
* enough: they answer for the same set of radios. */
|
|
24480
|
+
get _answered() {
|
|
24481
|
+
return this._groupHasSelection || this._grouping.answered;
|
|
24482
|
+
}
|
|
24483
|
+
connectedCallback() {
|
|
24484
|
+
super.connectedCallback();
|
|
24485
|
+
this.addEventListener("click", this._onClick);
|
|
24486
|
+
this.addEventListener("keydown", this._onKeyDown);
|
|
24487
|
+
this.addEventListener("focus", this._onFocus);
|
|
24488
|
+
this.addEventListener("blur", this._onBlur);
|
|
24489
|
+
}
|
|
24490
|
+
disconnectedCallback() {
|
|
24491
|
+
super.disconnectedCallback();
|
|
24492
|
+
this.removeEventListener("click", this._onClick);
|
|
24493
|
+
this.removeEventListener("keydown", this._onKeyDown);
|
|
24494
|
+
this.removeEventListener("focus", this._onFocus);
|
|
24495
|
+
this.removeEventListener("blur", this._onBlur);
|
|
24179
24496
|
}
|
|
24180
24497
|
firstUpdated() {
|
|
24181
24498
|
this._initialChecked = this.checked;
|
|
24182
24499
|
}
|
|
24500
|
+
/** Counting happens before the render, not after it: the place this field is
|
|
24501
|
+
* given decides what `updated()` writes on it, and a count from there would
|
|
24502
|
+
* ask for a second render of what was just drawn. */
|
|
24503
|
+
willUpdate(changed) {
|
|
24504
|
+
if (changed.has("checked") && this.checked) this._grouping.uncheckOthers();
|
|
24505
|
+
if (changed.has("name")) this._grouping.regroup();
|
|
24506
|
+
else if (changed.has("checked") || changed.has("disabled")) this._grouping.refresh();
|
|
24507
|
+
}
|
|
24508
|
+
updated(changed) {
|
|
24509
|
+
super.updated(changed);
|
|
24510
|
+
this._syncHostAria();
|
|
24511
|
+
}
|
|
24512
|
+
/**
|
|
24513
|
+
* This element is the radio, label and all.
|
|
24514
|
+
*
|
|
24515
|
+
* A native radio in a shadow root of its own is a group of one, which a
|
|
24516
|
+
* screen reader counts as "1 of 1" and Tab stops at one by one. The role on
|
|
24517
|
+
* the element itself puts the whole group back in one tree, and the label it
|
|
24518
|
+
* renders is the name it is announced by.
|
|
24519
|
+
*/
|
|
24520
|
+
_syncHostAria() {
|
|
24521
|
+
this.setAttribute("role", "radio");
|
|
24522
|
+
this.setAttribute("aria-checked", String(this.checked));
|
|
24523
|
+
this._appliedLabel = setOwnedAttribute(this, "aria-label", this.label, this._appliedLabel);
|
|
24524
|
+
if (this.disabled) this.setAttribute("aria-disabled", "true");
|
|
24525
|
+
else this.removeAttribute("aria-disabled");
|
|
24526
|
+
const position = this._position;
|
|
24527
|
+
if (position) {
|
|
24528
|
+
this.setAttribute("aria-posinset", String(position.posInSet));
|
|
24529
|
+
this.setAttribute("aria-setsize", String(position.setSize));
|
|
24530
|
+
} else {
|
|
24531
|
+
this.removeAttribute("aria-posinset");
|
|
24532
|
+
this.removeAttribute("aria-setsize");
|
|
24533
|
+
}
|
|
24534
|
+
const tabbable = !this.disabled && position?.tabbable !== false;
|
|
24535
|
+
this.setAttribute("tabindex", tabbable ? "0" : "-1");
|
|
24536
|
+
}
|
|
24183
24537
|
/** An unchecked radio contributes nothing to the form. The group calls
|
|
24184
24538
|
* `commitFormValue()` on the siblings it unchecks, so a listener that reads
|
|
24185
24539
|
* the form while `change` is still propagating never sees two values. */
|
|
@@ -24192,34 +24546,27 @@ var NLDDRadioButtonField = (_NLDDRadioButtonField = class NLDDRadioButtonField e
|
|
|
24192
24546
|
formStateRestoreCallback(state) {
|
|
24193
24547
|
this.checked = state !== null;
|
|
24194
24548
|
}
|
|
24195
|
-
|
|
24196
|
-
|
|
24197
|
-
|
|
24198
|
-
|
|
24199
|
-
|
|
24200
|
-
|
|
24201
|
-
this.checked = e.detail.checked;
|
|
24549
|
+
/** Checks this field, the way a click or the space bar does. The group hears
|
|
24550
|
+
* the change and unchecks the others; without a group the fields that share
|
|
24551
|
+
* its name are unchecked here. */
|
|
24552
|
+
select() {
|
|
24553
|
+
if (this.disabled || this.checked) return;
|
|
24554
|
+
this.checked = true;
|
|
24202
24555
|
this.commitFormValue();
|
|
24556
|
+
this._grouping.uncheckOthers();
|
|
24203
24557
|
this.dispatchEvent(new CustomEvent("change", {
|
|
24204
24558
|
detail: {
|
|
24205
|
-
checked:
|
|
24559
|
+
checked: true,
|
|
24206
24560
|
value: this.value
|
|
24207
24561
|
},
|
|
24208
24562
|
bubbles: true,
|
|
24209
24563
|
composed: true
|
|
24210
24564
|
}));
|
|
24211
24565
|
}
|
|
24212
|
-
/**
|
|
24213
|
-
*
|
|
24214
|
-
* its native input. Lets consumers call `radioFieldEl.focus()` without
|
|
24215
|
-
* reaching into shadow DOM.
|
|
24216
|
-
*/
|
|
24217
|
-
focus(options) {
|
|
24218
|
-
this.shadowRoot?.querySelector("nldd-radio-button")?.focus(options);
|
|
24219
|
-
}
|
|
24220
|
-
/** The radio button it renders knows which element inside itself is the control. */
|
|
24566
|
+
/** The element assistive software meets is this one, so the description and
|
|
24567
|
+
* the invalid state belong here rather than on the shape inside. */
|
|
24221
24568
|
describedTarget() {
|
|
24222
|
-
return this
|
|
24569
|
+
return this;
|
|
24223
24570
|
}
|
|
24224
24571
|
render() {
|
|
24225
24572
|
return radioButtonFieldTemplate(this);
|
|
@@ -24247,6 +24594,9 @@ __decorate([property({
|
|
|
24247
24594
|
type: Boolean,
|
|
24248
24595
|
reflect: true
|
|
24249
24596
|
})], NLDDRadioButtonField.prototype, "invalid", void 0);
|
|
24597
|
+
__decorate([state()], NLDDRadioButtonField.prototype, "_groupPosition", void 0);
|
|
24598
|
+
__decorate([state()], NLDDRadioButtonField.prototype, "_groupHasSelection", void 0);
|
|
24599
|
+
__decorate([state()], NLDDRadioButtonField.prototype, "_focusRing", void 0);
|
|
24250
24600
|
NLDDRadioButtonField = __decorate([customElement("nldd-radio-button-field")], NLDDRadioButtonField);
|
|
24251
24601
|
//#endregion
|
|
24252
24602
|
//#region src/components/inputs/radio-button-group/radio-button-group.styles.ts
|
|
@@ -24324,6 +24674,7 @@ var NLDDRadioButtonGroup = (_NLDDRadioButtonGroup = class NLDDRadioButtonGroup e
|
|
|
24324
24674
|
field.checked = false;
|
|
24325
24675
|
field.commitFormValue?.();
|
|
24326
24676
|
});
|
|
24677
|
+
this._positionFields();
|
|
24327
24678
|
};
|
|
24328
24679
|
this._handleKeyDown = (e) => {
|
|
24329
24680
|
if (e.key !== "ArrowDown" && e.key !== "ArrowUp" && e.key !== "ArrowRight" && e.key !== "ArrowLeft") return;
|
|
@@ -24338,7 +24689,8 @@ var NLDDRadioButtonGroup = (_NLDDRadioButtonGroup = class NLDDRadioButtonGroup e
|
|
|
24338
24689
|
activeField.commitFormValue?.();
|
|
24339
24690
|
nextField.checked = true;
|
|
24340
24691
|
nextField.commitFormValue?.();
|
|
24341
|
-
|
|
24692
|
+
this._positionFields();
|
|
24693
|
+
nextField.focus();
|
|
24342
24694
|
nextField.dispatchEvent(new CustomEvent("change", {
|
|
24343
24695
|
detail: {
|
|
24344
24696
|
checked: true,
|
|
@@ -24400,6 +24752,17 @@ var NLDDRadioButtonGroup = (_NLDDRadioButtonGroup = class NLDDRadioButtonGroup e
|
|
|
24400
24752
|
field.disabled = false;
|
|
24401
24753
|
}
|
|
24402
24754
|
});
|
|
24755
|
+
this._positionFields();
|
|
24756
|
+
}
|
|
24757
|
+
/** Each field its place in the group, and the group one stop for Tab. A
|
|
24758
|
+
* field cannot count its siblings: it only sees itself. */
|
|
24759
|
+
_positionFields() {
|
|
24760
|
+
const fields = this._getFields();
|
|
24761
|
+
const answered = fields.some((field) => field.checked);
|
|
24762
|
+
radioPositions(fields, (field) => field.checked, (field) => field.disabled).forEach((position, index) => {
|
|
24763
|
+
fields[index]._groupPosition = position;
|
|
24764
|
+
fields[index]._groupHasSelection = answered;
|
|
24765
|
+
});
|
|
24403
24766
|
}
|
|
24404
24767
|
/**
|
|
24405
24768
|
* Delegates focus to the checked option, or to the first enabled one when
|
|
@@ -24923,6 +25286,16 @@ var segmentedControlStyles = css`
|
|
|
24923
25286
|
|
|
24924
25287
|
/* # Elements */
|
|
24925
25288
|
|
|
25289
|
+
.segmented-control__validation-input {
|
|
25290
|
+
position: absolute;
|
|
25291
|
+
inset: 0;
|
|
25292
|
+
opacity: 0;
|
|
25293
|
+
margin: 0;
|
|
25294
|
+
pointer-events: none;
|
|
25295
|
+
width: 100%;
|
|
25296
|
+
height: 100%;
|
|
25297
|
+
}
|
|
25298
|
+
|
|
24926
25299
|
::slotted(nldd-segmented-control-item[selected]) {
|
|
24927
25300
|
position: relative;
|
|
24928
25301
|
z-index: var(--_selected-z-index);
|
|
@@ -25077,12 +25450,18 @@ var segmentedControlItemStyles = css`
|
|
|
25077
25450
|
opacity: var(--primitives-opacity-disabled);
|
|
25078
25451
|
}
|
|
25079
25452
|
|
|
25453
|
+
:host(:focus-visible) {
|
|
25454
|
+
outline: none;
|
|
25455
|
+
}
|
|
25456
|
+
|
|
25457
|
+
:host(:focus-visible) .segmented-control__item,
|
|
25080
25458
|
.segmented-control__item:has(:focus-visible) {
|
|
25081
25459
|
outline: var(--semantics-focus-ring-outline);
|
|
25082
25460
|
outline-offset: var(--semantics-focus-ring-outline-offset);
|
|
25083
25461
|
box-shadow: var(--semantics-focus-ring-box-shadow);
|
|
25084
25462
|
}
|
|
25085
25463
|
|
|
25464
|
+
:host([selected]:focus-visible) .segmented-control__item,
|
|
25086
25465
|
:host([selected]) .segmented-control__item:has(:focus-visible) {
|
|
25087
25466
|
box-shadow: var(--semantics-focus-ring-box-shadow), inset 0 0 0 var(--primitives-border-width-thin) var(--_highlight-border-color);
|
|
25088
25467
|
}
|
|
@@ -25138,35 +25517,48 @@ var segmentedControlItemStyles = css`
|
|
|
25138
25517
|
//#endregion
|
|
25139
25518
|
//#region src/components/inputs/segmented-control/segmented-control.template.ts
|
|
25140
25519
|
function segmentedControlTemplate(component) {
|
|
25141
|
-
return html
|
|
25520
|
+
return html`
|
|
25521
|
+
<input class="segmented-control__validation-input"
|
|
25522
|
+
type="radio"
|
|
25523
|
+
name="nldd-validation"
|
|
25524
|
+
?required=${component.required && component.type !== "checkbox"}
|
|
25525
|
+
?disabled=${component.disabled}
|
|
25526
|
+
.checked=${!!component.value}
|
|
25527
|
+
tabindex="-1"
|
|
25528
|
+
aria-hidden="true"
|
|
25529
|
+
>
|
|
25530
|
+
<slot @slotchange=${component._onSlotChange}></slot>
|
|
25531
|
+
`;
|
|
25142
25532
|
}
|
|
25143
25533
|
function segmentedControlItemTemplate(component) {
|
|
25144
25534
|
const isIcon = component.variant === "icon";
|
|
25145
25535
|
const showPlaceholder = isIcon || component.variant === "icon-and-text";
|
|
25146
25536
|
const labelText = component.text || nothing;
|
|
25147
|
-
const
|
|
25148
|
-
<
|
|
25149
|
-
|
|
25150
|
-
|
|
25151
|
-
|
|
25152
|
-
|
|
25153
|
-
|
|
25154
|
-
|
|
25155
|
-
|
|
25156
|
-
|
|
25157
|
-
|
|
25158
|
-
|
|
25159
|
-
<
|
|
25160
|
-
|
|
25161
|
-
|
|
25162
|
-
|
|
25163
|
-
|
|
25164
|
-
|
|
25165
|
-
|
|
25166
|
-
|
|
25167
|
-
|
|
25168
|
-
|
|
25169
|
-
|
|
25537
|
+
const content = html`
|
|
25538
|
+
<span class="segmented-control__item-icon"
|
|
25539
|
+
aria-hidden=${isIcon ? nothing : "true"}
|
|
25540
|
+
>
|
|
25541
|
+
${component.icon ? html`<nldd-icon name=${component.icon}></nldd-icon>` : html`<slot name="icon">${showPlaceholder ? html`<nldd-icon name="icon-placeholder"></nldd-icon>` : nothing}</slot>`}
|
|
25542
|
+
</span>
|
|
25543
|
+
<span class="segmented-control__item-text"
|
|
25544
|
+
aria-hidden=${isIcon ? "true" : nothing}
|
|
25545
|
+
>
|
|
25546
|
+
${component.text}
|
|
25547
|
+
</span>`;
|
|
25548
|
+
const label = component.inputType === "radio" ? html`<div class="segmented-control__item">${content}</div>` : html`
|
|
25549
|
+
<label class="segmented-control__item">
|
|
25550
|
+
<input class="segmented-control__item-input"
|
|
25551
|
+
type="checkbox"
|
|
25552
|
+
name=${component.groupName || nothing}
|
|
25553
|
+
value=${component.value}
|
|
25554
|
+
.checked=${component.selected}
|
|
25555
|
+
?disabled=${component.disabled}
|
|
25556
|
+
?required=${component.required}
|
|
25557
|
+
aria-label=${isIcon ? labelText : nothing}
|
|
25558
|
+
@change=${component._handleChange}
|
|
25559
|
+
>
|
|
25560
|
+
${content}
|
|
25561
|
+
</label>`;
|
|
25170
25562
|
if (isIcon && labelText) return html`<nldd-tooltip text=${labelText}>${label}</nldd-tooltip>`;
|
|
25171
25563
|
return label;
|
|
25172
25564
|
}
|
|
@@ -25182,11 +25574,11 @@ function segmentedControlItemTemplate(component) {
|
|
|
25182
25574
|
* @attr {string} value - Selected value for radio type
|
|
25183
25575
|
* @prop {string[]} values - Selected values for checkbox type (property binding only, not an attribute)
|
|
25184
25576
|
* @attr {string} size - Control size: 'sm' | 'md' | 'lg' (default: 'md')
|
|
25185
|
-
* @attr {string} type -
|
|
25577
|
+
* @attr {string} type - Selection mode: 'radio' | 'checkbox' (default: 'radio').
|
|
25186
25578
|
* @attr {string} variant - Content type for all items: 'text' | 'icon' | 'icon-and-text' (default: 'text')
|
|
25187
25579
|
* @attr {boolean} disabled - Disabled state for all items
|
|
25188
25580
|
* @attr {string} width - Width mode: 'full' (stretches to container), 'fit-content' (per-item content size), or any CSS length (e.g. '240px')
|
|
25189
|
-
* @attr {string} name - Name for form submission
|
|
25581
|
+
* @attr {string} name - Name for form submission
|
|
25190
25582
|
* @attr {string} accessible-label - Accessible name for the group, set as aria-label
|
|
25191
25583
|
* @attr {string} accessible-labeled-by - Id of an external label element, set as aria-labelledby on the group
|
|
25192
25584
|
* @attr {boolean} required - Marks the group as required. Enforced in radio mode; in checkbox mode only announced.
|
|
@@ -25206,9 +25598,9 @@ function segmentedControlItemTemplate(component) {
|
|
|
25206
25598
|
* @attr {string} icon - Icon name for nldd-icon
|
|
25207
25599
|
* @attr {string} size - Control size: 'sm' | 'md' | 'lg' (default: 'md'). Set by nldd-segmented-control.
|
|
25208
25600
|
* @attr {string} variant - Content type: 'text' | 'icon' | 'icon-and-text' (default: 'text'). Set by nldd-segmented-control.
|
|
25209
|
-
* @attr {string} input-type -
|
|
25210
|
-
* @attr {string} group-name - Name of the group for form submission, put on the native
|
|
25211
|
-
* @attr {boolean} required - Required state.
|
|
25601
|
+
* @attr {string} input-type - Selection mode: 'radio' | 'checkbox' (default: 'radio'). In radio mode the item is the radio itself, in checkbox mode it renders a native checkbox. Set by nldd-segmented-control.
|
|
25602
|
+
* @attr {string} group-name - Name of the group for form submission, put on the native checkbox. Set by nldd-segmented-control.
|
|
25603
|
+
* @attr {boolean} required - Required state of the native checkbox. In radio mode the group carries the constraint.
|
|
25212
25604
|
*
|
|
25213
25605
|
* @slot icon - Slot for a custom icon (e.g. custom SVG). Only used when icon attribute is not set.
|
|
25214
25606
|
*
|
|
@@ -25230,6 +25622,102 @@ var NLDDSegmentedControlItem = (_NLDDSegmentedControlItem = class NLDDSegmentedC
|
|
|
25230
25622
|
this.groupName = "";
|
|
25231
25623
|
this.text = "";
|
|
25232
25624
|
this.icon = "";
|
|
25625
|
+
this._groupPosition = null;
|
|
25626
|
+
this._appliedRadioLabel = null;
|
|
25627
|
+
this._onRadioFocus = () => {
|
|
25628
|
+
this._tooltip?._handleFocusIn();
|
|
25629
|
+
};
|
|
25630
|
+
this._onRadioBlur = () => {
|
|
25631
|
+
this._tooltip?._handleTriggerLeave();
|
|
25632
|
+
};
|
|
25633
|
+
this._onRadioClick = () => {
|
|
25634
|
+
if (this.inputType !== "radio") return;
|
|
25635
|
+
this._select();
|
|
25636
|
+
};
|
|
25637
|
+
this._onRadioKeyDown = (e) => {
|
|
25638
|
+
if (this.inputType !== "radio" || this.disabled) return;
|
|
25639
|
+
if (e.key === " ") {
|
|
25640
|
+
e.preventDefault();
|
|
25641
|
+
this._select();
|
|
25642
|
+
return;
|
|
25643
|
+
}
|
|
25644
|
+
if (e.key === "Escape") this._tooltip?._handleTriggerLeave();
|
|
25645
|
+
};
|
|
25646
|
+
}
|
|
25647
|
+
connectedCallback() {
|
|
25648
|
+
super.connectedCallback();
|
|
25649
|
+
this.addEventListener("click", this._onRadioClick);
|
|
25650
|
+
this.addEventListener("keydown", this._onRadioKeyDown);
|
|
25651
|
+
this.addEventListener("focus", this._onRadioFocus);
|
|
25652
|
+
this.addEventListener("blur", this._onRadioBlur);
|
|
25653
|
+
}
|
|
25654
|
+
disconnectedCallback() {
|
|
25655
|
+
super.disconnectedCallback();
|
|
25656
|
+
this.removeEventListener("click", this._onRadioClick);
|
|
25657
|
+
this.removeEventListener("keydown", this._onRadioKeyDown);
|
|
25658
|
+
this.removeEventListener("focus", this._onRadioFocus);
|
|
25659
|
+
this.removeEventListener("blur", this._onRadioBlur);
|
|
25660
|
+
}
|
|
25661
|
+
/**
|
|
25662
|
+
* The tooltip of the icon variant, which shows itself when focus moves into
|
|
25663
|
+
* it. In radio mode the focus is on this element, outside it, so it hears
|
|
25664
|
+
* about focus and Escape from here.
|
|
25665
|
+
*/
|
|
25666
|
+
get _tooltip() {
|
|
25667
|
+
return this.inputType === "radio" ? this.shadowRoot?.querySelector("nldd-tooltip") ?? null : null;
|
|
25668
|
+
}
|
|
25669
|
+
updated() {
|
|
25670
|
+
this._syncRadioAria();
|
|
25671
|
+
}
|
|
25672
|
+
/**
|
|
25673
|
+
* In radio mode this element is the radio.
|
|
25674
|
+
*
|
|
25675
|
+
* A native radio in a shadow root of its own is a group of one: a screen
|
|
25676
|
+
* reader counts it as "1 of 1" and Tab stops at every option. The role on the
|
|
25677
|
+
* element itself puts the whole group back in one tree, under the radiogroup
|
|
25678
|
+
* the control carries. Checkbox mode keeps its native input.
|
|
25679
|
+
*/
|
|
25680
|
+
_syncRadioAria() {
|
|
25681
|
+
if (this.inputType !== "radio") {
|
|
25682
|
+
for (const name of [
|
|
25683
|
+
"role",
|
|
25684
|
+
"aria-checked",
|
|
25685
|
+
"aria-disabled",
|
|
25686
|
+
"aria-posinset",
|
|
25687
|
+
"aria-setsize",
|
|
25688
|
+
"tabindex"
|
|
25689
|
+
]) this.removeAttribute(name);
|
|
25690
|
+
this._appliedRadioLabel = setOwnedAttribute(this, "aria-label", "", this._appliedRadioLabel);
|
|
25691
|
+
return;
|
|
25692
|
+
}
|
|
25693
|
+
this.setAttribute("role", "radio");
|
|
25694
|
+
this.setAttribute("aria-checked", String(this.selected));
|
|
25695
|
+
this._appliedRadioLabel = setOwnedAttribute(this, "aria-label", this.text, this._appliedRadioLabel);
|
|
25696
|
+
if (this.disabled) this.setAttribute("aria-disabled", "true");
|
|
25697
|
+
else this.removeAttribute("aria-disabled");
|
|
25698
|
+
const position = this._groupPosition;
|
|
25699
|
+
if (position) {
|
|
25700
|
+
this.setAttribute("aria-posinset", String(position.posInSet));
|
|
25701
|
+
this.setAttribute("aria-setsize", String(position.setSize));
|
|
25702
|
+
} else {
|
|
25703
|
+
this.removeAttribute("aria-posinset");
|
|
25704
|
+
this.removeAttribute("aria-setsize");
|
|
25705
|
+
}
|
|
25706
|
+
const tabbable = !this.disabled && position?.tabbable !== false;
|
|
25707
|
+
this.setAttribute("tabindex", tabbable ? "0" : "-1");
|
|
25708
|
+
}
|
|
25709
|
+
/** Checked, the way a click or the space bar does it. A radio never
|
|
25710
|
+
* unchecks itself, so an option that is already selected says nothing. */
|
|
25711
|
+
_select() {
|
|
25712
|
+
if (this.disabled || this.selected) return;
|
|
25713
|
+
this.dispatchEvent(new CustomEvent("item-change", {
|
|
25714
|
+
detail: {
|
|
25715
|
+
value: this.value,
|
|
25716
|
+
checked: true
|
|
25717
|
+
},
|
|
25718
|
+
bubbles: true,
|
|
25719
|
+
composed: true
|
|
25720
|
+
}));
|
|
25233
25721
|
}
|
|
25234
25722
|
_handleChange(e) {
|
|
25235
25723
|
const input = e.target;
|
|
@@ -25243,10 +25731,15 @@ var NLDDSegmentedControlItem = (_NLDDSegmentedControlItem = class NLDDSegmentedC
|
|
|
25243
25731
|
}));
|
|
25244
25732
|
}
|
|
25245
25733
|
/**
|
|
25246
|
-
* Delegates focus to
|
|
25734
|
+
* Delegates focus to whichever control this mode renders: the element itself
|
|
25735
|
+
* in radio mode, the native `<input>` in checkbox mode. Lets consumers call
|
|
25247
25736
|
* `itemEl.focus()` without reaching into shadow DOM.
|
|
25248
25737
|
*/
|
|
25249
25738
|
focus(options) {
|
|
25739
|
+
if (this.inputType === "radio") {
|
|
25740
|
+
super.focus(options);
|
|
25741
|
+
return;
|
|
25742
|
+
}
|
|
25250
25743
|
this.shadowRoot?.querySelector(".segmented-control__item-input")?.focus(options);
|
|
25251
25744
|
}
|
|
25252
25745
|
render() {
|
|
@@ -25289,6 +25782,7 @@ __decorate([property({
|
|
|
25289
25782
|
converter: reflectNonDefault("")
|
|
25290
25783
|
})], NLDDSegmentedControlItem.prototype, "text", void 0);
|
|
25291
25784
|
__decorate([property({ type: String })], NLDDSegmentedControlItem.prototype, "icon", void 0);
|
|
25785
|
+
__decorate([state()], NLDDSegmentedControlItem.prototype, "_groupPosition", void 0);
|
|
25292
25786
|
NLDDSegmentedControlItem = __decorate([customElement("nldd-segmented-control-item")], NLDDSegmentedControlItem);
|
|
25293
25787
|
var NLDDSegmentedControl = (_NLDDSegmentedControl2 = class NLDDSegmentedControl extends FormAssociated(LitElement) {
|
|
25294
25788
|
constructor(..._args2) {
|
|
@@ -25338,6 +25832,10 @@ var NLDDSegmentedControl = (_NLDDSegmentedControl2 = class NLDDSegmentedControl
|
|
|
25338
25832
|
};
|
|
25339
25833
|
this._handleKeydown = (e) => {
|
|
25340
25834
|
if (this.disabled || this.type === "checkbox") return;
|
|
25835
|
+
if (e.key === "Enter") {
|
|
25836
|
+
submitOnEnter(this, e);
|
|
25837
|
+
return;
|
|
25838
|
+
}
|
|
25341
25839
|
const items = this._getItems().filter((item) => !item.disabled);
|
|
25342
25840
|
if (items.length === 0) return;
|
|
25343
25841
|
const currentIndex = items.findIndex((item) => item.selected);
|
|
@@ -25367,7 +25865,7 @@ var NLDDSegmentedControl = (_NLDDSegmentedControl2 = class NLDDSegmentedControl
|
|
|
25367
25865
|
this.value = items[nextIndex].value;
|
|
25368
25866
|
this._syncItems();
|
|
25369
25867
|
this.commitFormValue();
|
|
25370
|
-
items[nextIndex].
|
|
25868
|
+
items[nextIndex].focus();
|
|
25371
25869
|
this.dispatchEvent(new CustomEvent("change", {
|
|
25372
25870
|
detail: { value: this.value },
|
|
25373
25871
|
bubbles: true,
|
|
@@ -25435,9 +25933,6 @@ var NLDDSegmentedControl = (_NLDDSegmentedControl2 = class NLDDSegmentedControl
|
|
|
25435
25933
|
}
|
|
25436
25934
|
_syncItems() {
|
|
25437
25935
|
const items = this._getItems();
|
|
25438
|
-
items.forEach((item) => {
|
|
25439
|
-
item.required = this.required && this.type !== "checkbox";
|
|
25440
|
-
});
|
|
25441
25936
|
this.toggleAttribute("aria-required", this.required);
|
|
25442
25937
|
if (this.invalid) this.setAttribute("aria-invalid", "true");
|
|
25443
25938
|
else this.removeAttribute("aria-invalid");
|
|
@@ -25461,6 +25956,10 @@ var NLDDSegmentedControl = (_NLDDSegmentedControl2 = class NLDDSegmentedControl
|
|
|
25461
25956
|
item.groupName = this.name || this._autoName;
|
|
25462
25957
|
item.selected = this.type === "checkbox" ? selectedValues.includes(item.value) : item.value === this.value;
|
|
25463
25958
|
});
|
|
25959
|
+
const positions = this.type === "radio" ? radioPositions(items, (item) => item.selected, (item) => item.disabled) : [];
|
|
25960
|
+
items.forEach((item, index) => {
|
|
25961
|
+
item._groupPosition = positions[index] ?? null;
|
|
25962
|
+
});
|
|
25464
25963
|
}
|
|
25465
25964
|
get _autoName() {
|
|
25466
25965
|
if (!this._generatedName) this._generatedName = `nldd-segmented-${_NLDDSegmentedControl._idCounter++}`;
|
|
@@ -28881,7 +29380,7 @@ function completionSource(getTypeaheads, onChoose) {
|
|
|
28881
29380
|
}
|
|
28882
29381
|
var popupTheme = EditorView.theme({
|
|
28883
29382
|
".cm-tooltip.cm-tooltip-autocomplete": {
|
|
28884
|
-
border: "
|
|
29383
|
+
border: "none",
|
|
28885
29384
|
borderRadius: "var(--semantics-overlays-corner-radius)",
|
|
28886
29385
|
backgroundColor: "var(--semantics-surfaces-base-background-color)",
|
|
28887
29386
|
boxShadow: "var(--components-menu-box-shadow)"
|
|
@@ -30556,11 +31055,13 @@ var stableCursor = [
|
|
|
30556
31055
|
head,
|
|
30557
31056
|
side: "pointer"
|
|
30558
31057
|
};
|
|
30559
|
-
else if (tr.isUserEvent("input") || tr.isUserEvent("delete.backward"))
|
|
30560
|
-
head
|
|
30561
|
-
|
|
30562
|
-
|
|
30563
|
-
|
|
31058
|
+
else if (tr.isUserEvent("input") || tr.isUserEvent("delete.backward")) {
|
|
31059
|
+
const grew = tr.isUserEvent("input") && bareUrlEndsAt(update.state, head);
|
|
31060
|
+
hint = {
|
|
31061
|
+
head,
|
|
31062
|
+
side: linkEndsAt(update.state, head) && !linkEndsAt(update.startState, oldHead) && !grew ? 1 : -1
|
|
31063
|
+
};
|
|
31064
|
+
} else if (tr.isUserEvent("delete.forward")) hint = {
|
|
30564
31065
|
head,
|
|
30565
31066
|
side: 1
|
|
30566
31067
|
};
|
|
@@ -34630,6 +35131,7 @@ var toggleButtonStyles = css`
|
|
|
34630
35131
|
|
|
34631
35132
|
${inheritedTextReset}
|
|
34632
35133
|
display: inline-block;
|
|
35134
|
+
position: relative;
|
|
34633
35135
|
-webkit-user-select: none;
|
|
34634
35136
|
user-select: none;
|
|
34635
35137
|
-webkit-tap-highlight-color: transparent;
|
|
@@ -34639,6 +35141,10 @@ var toggleButtonStyles = css`
|
|
|
34639
35141
|
display: none;
|
|
34640
35142
|
}
|
|
34641
35143
|
|
|
35144
|
+
:host(:focus-visible) {
|
|
35145
|
+
outline: none;
|
|
35146
|
+
}
|
|
35147
|
+
|
|
34642
35148
|
:host([size="xs"]) {
|
|
34643
35149
|
--_corner-radius: var(--semantics-controls-xs-corner-radius);
|
|
34644
35150
|
--_min-size: var(--semantics-controls-xs-min-size);
|
|
@@ -34748,7 +35254,8 @@ var toggleButtonStyles = css`
|
|
|
34748
35254
|
}
|
|
34749
35255
|
|
|
34750
35256
|
.toggle-button:focus-visible,
|
|
34751
|
-
.toggle-button:has(.toggle-button__input:focus-visible)
|
|
35257
|
+
.toggle-button:has(.toggle-button__input:focus-visible),
|
|
35258
|
+
:host(:focus-visible) .toggle-button {
|
|
34752
35259
|
outline: var(--semantics-focus-ring-outline);
|
|
34753
35260
|
outline-offset: var(--semantics-focus-ring-outline-offset);
|
|
34754
35261
|
box-shadow: var(--semantics-focus-ring-box-shadow), inset 0 0 0 var(--primitives-border-width-thin) var(--_highlight-border-color);
|
|
@@ -34808,6 +35315,16 @@ var toggleButtonStyles = css`
|
|
|
34808
35315
|
width: 100%;
|
|
34809
35316
|
height: 100%;
|
|
34810
35317
|
}
|
|
35318
|
+
|
|
35319
|
+
.toggle-button__validation-input {
|
|
35320
|
+
position: absolute;
|
|
35321
|
+
inset: 0;
|
|
35322
|
+
opacity: 0;
|
|
35323
|
+
margin: 0;
|
|
35324
|
+
pointer-events: none;
|
|
35325
|
+
width: 100%;
|
|
35326
|
+
height: 100%;
|
|
35327
|
+
}
|
|
34811
35328
|
`;
|
|
34812
35329
|
//#endregion
|
|
34813
35330
|
//#region src/components/inputs/toggle-button/toggle-button.template.ts
|
|
@@ -34826,10 +35343,25 @@ function toggleButtonTemplate(component) {
|
|
|
34826
35343
|
></nldd-icon>` : nothing}</slot>`;
|
|
34827
35344
|
const textContent = showText ? html`<span class="toggle-button__text">${component.text}</span>` : nothing;
|
|
34828
35345
|
let result;
|
|
34829
|
-
if (component.type === "
|
|
35346
|
+
if (component.type === "radio") result = html`
|
|
35347
|
+
<div class="toggle-button">
|
|
35348
|
+
<input class="toggle-button__validation-input"
|
|
35349
|
+
type="radio"
|
|
35350
|
+
name="nldd-validation"
|
|
35351
|
+
?required=${component.required}
|
|
35352
|
+
?disabled=${component.disabled}
|
|
35353
|
+
.checked=${component._groupHasSelection}
|
|
35354
|
+
tabindex="-1"
|
|
35355
|
+
aria-hidden="true"
|
|
35356
|
+
>
|
|
35357
|
+
${icon}
|
|
35358
|
+
${textContent}
|
|
35359
|
+
</div>
|
|
35360
|
+
`;
|
|
35361
|
+
else if (component.type === "checkbox") result = html`
|
|
34830
35362
|
<label class="toggle-button">
|
|
34831
35363
|
<input class="toggle-button__input"
|
|
34832
|
-
type
|
|
35364
|
+
type="checkbox"
|
|
34833
35365
|
.checked=${component.selected}
|
|
34834
35366
|
?disabled=${component.disabled}
|
|
34835
35367
|
?required=${component.required}
|
|
@@ -34865,11 +35397,15 @@ function toggleButtonTemplate(component) {
|
|
|
34865
35397
|
* Nederlandse Digitale Dienst Toggle Button Component (Lit + TypeScript)
|
|
34866
35398
|
*
|
|
34867
35399
|
* A selectable button that toggles between selected and unselected.
|
|
34868
|
-
* Available as a button (aria-pressed), checkbox, or radio
|
|
35400
|
+
* Available as a button (aria-pressed), a checkbox, or a radio.
|
|
35401
|
+
*
|
|
35402
|
+
* In radio mode the button itself is the radio: it carries the role, the state
|
|
35403
|
+
* and its place in the group. A native radio in a shadow root of its own would
|
|
35404
|
+
* be a group of one, counted as "1 of 1" and stopped at by Tab.
|
|
34869
35405
|
*
|
|
34870
35406
|
* @element nldd-toggle-button
|
|
34871
35407
|
*
|
|
34872
|
-
* @attr {'button' | 'checkbox' | 'radio'} type -
|
|
35408
|
+
* @attr {'button' | 'checkbox' | 'radio'} type - What the button is: a button with aria-pressed, a native checkbox, or a radio (default: 'button')
|
|
34873
35409
|
* @attr {'xs' | 'sm' | 'md' | 'lg'} size - Button size (default: 'md')
|
|
34874
35410
|
* @attr {boolean} selected - Selected state
|
|
34875
35411
|
* @attr {boolean} disabled - Disabled state
|
|
@@ -34907,6 +35443,32 @@ var NLDDToggleButton = (_NLDDToggleButton = class NLDDToggleButton extends Descr
|
|
|
34907
35443
|
this._warnedA11y = false;
|
|
34908
35444
|
this._warnedEmptyIcon = false;
|
|
34909
35445
|
this.invalid = false;
|
|
35446
|
+
this._groupPosition = null;
|
|
35447
|
+
this._groupHasSelection = false;
|
|
35448
|
+
this._appliedRadioLabel = null;
|
|
35449
|
+
this._onRadioClick = () => {
|
|
35450
|
+
if (this.type !== "radio") return;
|
|
35451
|
+
this.toggle();
|
|
35452
|
+
};
|
|
35453
|
+
this._onRadioKeyDown = (e) => {
|
|
35454
|
+
if (this.type !== "radio" || this.disabled) return;
|
|
35455
|
+
if (e.key === " ") {
|
|
35456
|
+
e.preventDefault();
|
|
35457
|
+
this.toggle();
|
|
35458
|
+
return;
|
|
35459
|
+
}
|
|
35460
|
+
if (e.key === "Enter") {
|
|
35461
|
+
submitOnEnter(this, e);
|
|
35462
|
+
return;
|
|
35463
|
+
}
|
|
35464
|
+
if (e.key === "Escape") this._tooltip?._handleTriggerLeave();
|
|
35465
|
+
};
|
|
35466
|
+
this._onRadioFocus = () => {
|
|
35467
|
+
this._tooltip?._handleFocusIn();
|
|
35468
|
+
};
|
|
35469
|
+
this._onRadioBlur = () => {
|
|
35470
|
+
this._tooltip?._handleTriggerLeave();
|
|
35471
|
+
};
|
|
34910
35472
|
}
|
|
34911
35473
|
/** Whether an icon is present via attribute or slot. */
|
|
34912
35474
|
get _hasIcon() {
|
|
@@ -34922,6 +35484,44 @@ var NLDDToggleButton = (_NLDDToggleButton = class NLDDToggleButton extends Descr
|
|
|
34922
35484
|
if (!(!this.text && !this.accessibleLabel)) this._warnedA11y = false;
|
|
34923
35485
|
if (this._hasIcon || this.variant !== "icon") this._warnedEmptyIcon = false;
|
|
34924
35486
|
if (changed.has("selected") || changed.has("value") || changed.has("type")) this.commitFormValue();
|
|
35487
|
+
this._syncRadioAria();
|
|
35488
|
+
}
|
|
35489
|
+
/**
|
|
35490
|
+
* In radio mode this element is the radio.
|
|
35491
|
+
*
|
|
35492
|
+
* A native radio in a shadow root of its own is a group of one: a screen
|
|
35493
|
+
* reader counts it as "1 of 1" and Tab stops at every option. The role on the
|
|
35494
|
+
* element itself puts the group back in one tree. The other two types keep
|
|
35495
|
+
* the control they render: a checkbox input, or a button with aria-pressed.
|
|
35496
|
+
*/
|
|
35497
|
+
_syncRadioAria() {
|
|
35498
|
+
if (this.type !== "radio") {
|
|
35499
|
+
for (const name of [
|
|
35500
|
+
"role",
|
|
35501
|
+
"aria-checked",
|
|
35502
|
+
"aria-disabled",
|
|
35503
|
+
"aria-posinset",
|
|
35504
|
+
"aria-setsize",
|
|
35505
|
+
"tabindex"
|
|
35506
|
+
]) this.removeAttribute(name);
|
|
35507
|
+
this._appliedRadioLabel = setOwnedAttribute(this, "aria-label", "", this._appliedRadioLabel);
|
|
35508
|
+
return;
|
|
35509
|
+
}
|
|
35510
|
+
this.setAttribute("role", "radio");
|
|
35511
|
+
this.setAttribute("aria-checked", String(this.selected));
|
|
35512
|
+
this._appliedRadioLabel = setOwnedAttribute(this, "aria-label", this.accessibleLabel || this.text, this._appliedRadioLabel);
|
|
35513
|
+
if (this.disabled) this.setAttribute("aria-disabled", "true");
|
|
35514
|
+
else this.removeAttribute("aria-disabled");
|
|
35515
|
+
const position = this._groupPosition;
|
|
35516
|
+
if (position) {
|
|
35517
|
+
this.setAttribute("aria-posinset", String(position.posInSet));
|
|
35518
|
+
this.setAttribute("aria-setsize", String(position.setSize));
|
|
35519
|
+
} else {
|
|
35520
|
+
this.removeAttribute("aria-posinset");
|
|
35521
|
+
this.removeAttribute("aria-setsize");
|
|
35522
|
+
}
|
|
35523
|
+
const tabbable = !this.disabled && !this.noTab && position?.tabbable !== false;
|
|
35524
|
+
this.setAttribute("tabindex", tabbable ? "0" : "-1");
|
|
34925
35525
|
}
|
|
34926
35526
|
formValue() {
|
|
34927
35527
|
return (this.type === "checkbox" || this.type === "radio") && this.selected ? this.value : null;
|
|
@@ -34932,6 +35532,28 @@ var NLDDToggleButton = (_NLDDToggleButton = class NLDDToggleButton extends Descr
|
|
|
34932
35532
|
formStateRestoreCallback(state) {
|
|
34933
35533
|
this.selected = state !== null;
|
|
34934
35534
|
}
|
|
35535
|
+
connectedCallback() {
|
|
35536
|
+
super.connectedCallback();
|
|
35537
|
+
this.addEventListener("click", this._onRadioClick);
|
|
35538
|
+
this.addEventListener("keydown", this._onRadioKeyDown);
|
|
35539
|
+
this.addEventListener("focus", this._onRadioFocus);
|
|
35540
|
+
this.addEventListener("blur", this._onRadioBlur);
|
|
35541
|
+
}
|
|
35542
|
+
disconnectedCallback() {
|
|
35543
|
+
super.disconnectedCallback();
|
|
35544
|
+
this.removeEventListener("click", this._onRadioClick);
|
|
35545
|
+
this.removeEventListener("keydown", this._onRadioKeyDown);
|
|
35546
|
+
this.removeEventListener("focus", this._onRadioFocus);
|
|
35547
|
+
this.removeEventListener("blur", this._onRadioBlur);
|
|
35548
|
+
}
|
|
35549
|
+
/**
|
|
35550
|
+
* The tooltip of an icon-only button, which shows itself when focus moves
|
|
35551
|
+
* into it. In radio mode the focus is on this element, outside it, so it
|
|
35552
|
+
* hears about focus and Escape from here.
|
|
35553
|
+
*/
|
|
35554
|
+
get _tooltip() {
|
|
35555
|
+
return this.type === "radio" ? this.shadowRoot?.querySelector("nldd-tooltip") ?? null : null;
|
|
35556
|
+
}
|
|
34935
35557
|
_handleButtonClick() {
|
|
34936
35558
|
if (this.disabled) return;
|
|
34937
35559
|
this._toggle();
|
|
@@ -34972,6 +35594,10 @@ var NLDDToggleButton = (_NLDDToggleButton = class NLDDToggleButton extends Descr
|
|
|
34972
35594
|
* reaching into shadow DOM.
|
|
34973
35595
|
*/
|
|
34974
35596
|
focus(options) {
|
|
35597
|
+
if (this.type === "radio") {
|
|
35598
|
+
super.focus(options);
|
|
35599
|
+
return;
|
|
35600
|
+
}
|
|
34975
35601
|
this.shadowRoot?.querySelector(".toggle-button__input, button.toggle-button")?.focus(options);
|
|
34976
35602
|
}
|
|
34977
35603
|
/** The button is the control, not the input that carries the value. */
|
|
@@ -35029,6 +35655,8 @@ __decorate([property({
|
|
|
35029
35655
|
type: Boolean,
|
|
35030
35656
|
reflect: true
|
|
35031
35657
|
})], NLDDToggleButton.prototype, "invalid", void 0);
|
|
35658
|
+
__decorate([state()], NLDDToggleButton.prototype, "_groupPosition", void 0);
|
|
35659
|
+
__decorate([state()], NLDDToggleButton.prototype, "_groupHasSelection", void 0);
|
|
35032
35660
|
NLDDToggleButton = __decorate([customElement("nldd-toggle-button")], NLDDToggleButton);
|
|
35033
35661
|
//#endregion
|
|
35034
35662
|
//#region src/components/inputs/toggle-button-group/toggle-button-group.styles.ts
|
|
@@ -35125,6 +35753,7 @@ var NLDDToggleButtonGroup = (_NLDDToggleButtonGroup = class NLDDToggleButtonGrou
|
|
|
35125
35753
|
button.selected = false;
|
|
35126
35754
|
button.commitFormValue?.();
|
|
35127
35755
|
});
|
|
35756
|
+
this._positionButtons();
|
|
35128
35757
|
};
|
|
35129
35758
|
this._handleKeyDown = (e) => {
|
|
35130
35759
|
if (this.type !== "radio") return;
|
|
@@ -35147,7 +35776,8 @@ var NLDDToggleButtonGroup = (_NLDDToggleButtonGroup = class NLDDToggleButtonGrou
|
|
|
35147
35776
|
}
|
|
35148
35777
|
nextButton.selected = true;
|
|
35149
35778
|
nextButton.commitFormValue?.();
|
|
35150
|
-
|
|
35779
|
+
this._positionButtons();
|
|
35780
|
+
nextButton.focus();
|
|
35151
35781
|
nextButton.dispatchEvent(new CustomEvent("change", {
|
|
35152
35782
|
detail: {
|
|
35153
35783
|
selected: true,
|
|
@@ -35210,6 +35840,24 @@ var NLDDToggleButtonGroup = (_NLDDToggleButtonGroup = class NLDDToggleButtonGrou
|
|
|
35210
35840
|
button.disabled = false;
|
|
35211
35841
|
}
|
|
35212
35842
|
});
|
|
35843
|
+
this._positionButtons();
|
|
35844
|
+
}
|
|
35845
|
+
/** In radio mode, each button its place in the group and the group one stop
|
|
35846
|
+
* for Tab. Every button renders its input in a shadow root of its own, so
|
|
35847
|
+
* the browser would count each as a group of one. */
|
|
35848
|
+
_positionButtons() {
|
|
35849
|
+
const buttons = this._getButtons();
|
|
35850
|
+
if (this.type !== "radio") {
|
|
35851
|
+
buttons.forEach((button) => {
|
|
35852
|
+
button._groupPosition = null;
|
|
35853
|
+
});
|
|
35854
|
+
return;
|
|
35855
|
+
}
|
|
35856
|
+
const answered = buttons.some((button) => button.selected);
|
|
35857
|
+
radioPositions(buttons, (button) => button.selected, (button) => button.disabled).forEach((position, index) => {
|
|
35858
|
+
buttons[index]._groupPosition = position;
|
|
35859
|
+
buttons[index]._groupHasSelection = answered;
|
|
35860
|
+
});
|
|
35213
35861
|
}
|
|
35214
35862
|
/**
|
|
35215
35863
|
* Delegates focus to the first selected button, or to the first enabled one
|
|
@@ -38398,6 +39046,7 @@ var pageStyles = css`
|
|
|
38398
39046
|
--_outer-inset-bottom: var(--context-inset-bottom, 0px);
|
|
38399
39047
|
--_header-height: 0px;
|
|
38400
39048
|
--_footer-height: 0px;
|
|
39049
|
+
--_header-full-height: 0px;
|
|
38401
39050
|
/* Set from JS while the page owns the scroller. initial, not a length, so
|
|
38402
39051
|
the fallback stands when the document scrolls instead. */
|
|
38403
39052
|
--_scroll-height: initial;
|
|
@@ -38551,6 +39200,7 @@ var pageStyles = css`
|
|
|
38551
39200
|
:host(:not([data-scroll="root"])) .page__scroll {
|
|
38552
39201
|
--context-inset-top: var(--_header-height);
|
|
38553
39202
|
--context-inset-bottom: var(--_footer-height);
|
|
39203
|
+
padding-top: var(--_header-full-height);
|
|
38554
39204
|
}
|
|
38555
39205
|
|
|
38556
39206
|
:host([sticky-header]) .page__scroll {
|
|
@@ -38626,8 +39276,9 @@ function pageTemplate(component) {
|
|
|
38626
39276
|
* A page layout with optional sticky header and footer.
|
|
38627
39277
|
* Without sticky-header, the host is the scroll container and the header
|
|
38628
39278
|
* is in normal flow. With sticky-header, the header becomes absolute and
|
|
38629
|
-
* .page__scroll takes over scrolling
|
|
38630
|
-
*
|
|
39279
|
+
* .page__scroll takes over scrolling, padded by the measured header height.
|
|
39280
|
+
* That padding freezes once you scroll, so a collapsing bar cannot drag the
|
|
39281
|
+
* content up under the cursor.
|
|
38631
39282
|
*
|
|
38632
39283
|
* In root-scroll mode (--context-scroll-mode: root, derived upstream by
|
|
38633
39284
|
* nldd-app-view) the page stops owning a scroller: the document scrolls and the
|
|
@@ -38665,8 +39316,8 @@ var NLDDPage = (_NLDDPage = class NLDDPage extends LitElement {
|
|
|
38665
39316
|
this._scrollMode = "nested";
|
|
38666
39317
|
this._scrollTarget = null;
|
|
38667
39318
|
this._scrollProvider = null;
|
|
38668
|
-
this._headerObserver = null;
|
|
38669
39319
|
this._insetObserver = null;
|
|
39320
|
+
this._headerFullHeight = 0;
|
|
38670
39321
|
this._mainSlot = null;
|
|
38671
39322
|
this._resizeRaf = 0;
|
|
38672
39323
|
this._onResize = () => {
|
|
@@ -38741,7 +39392,6 @@ var NLDDPage = (_NLDDPage = class NLDDPage extends LitElement {
|
|
|
38741
39392
|
this._scrollProvider = null;
|
|
38742
39393
|
if (this._resizeRaf) cancelAnimationFrame(this._resizeRaf);
|
|
38743
39394
|
this._teardownScrollListener();
|
|
38744
|
-
this._teardownHeaderObserver();
|
|
38745
39395
|
this._teardownInsetObserver();
|
|
38746
39396
|
this._teardownMainSlotListener();
|
|
38747
39397
|
}
|
|
@@ -38769,10 +39419,8 @@ var NLDDPage = (_NLDDPage = class NLDDPage extends LitElement {
|
|
|
38769
39419
|
}
|
|
38770
39420
|
_configureScroll() {
|
|
38771
39421
|
this._teardownScrollListener();
|
|
38772
|
-
this._teardownHeaderObserver();
|
|
38773
|
-
if (this._scrollEl) this._scrollEl.style.paddingTop = "";
|
|
38774
39422
|
this._setupScrollListener();
|
|
38775
|
-
|
|
39423
|
+
this._headerFullHeight = 0;
|
|
38776
39424
|
this._setupInsetObserver();
|
|
38777
39425
|
this._onScroll();
|
|
38778
39426
|
}
|
|
@@ -38784,6 +39432,11 @@ var NLDDPage = (_NLDDPage = class NLDDPage extends LitElement {
|
|
|
38784
39432
|
* its anchor, and a consumer that had written the number down would be wrong
|
|
38785
39433
|
* from that moment on. A bar that is not sticky scrolls away and adds
|
|
38786
39434
|
* nothing, so it stays at zero.
|
|
39435
|
+
*
|
|
39436
|
+
* It also measures the padding the scroll wrapper reserves for an absolute
|
|
39437
|
+
* sticky header. One observer owns both, because two of them writing styles
|
|
39438
|
+
* that resize what the other watches is what leaves ResizeObserver
|
|
39439
|
+
* notifications undelivered.
|
|
38787
39440
|
*/
|
|
38788
39441
|
_setupInsetObserver() {
|
|
38789
39442
|
this._teardownInsetObserver();
|
|
@@ -38791,10 +39444,11 @@ var NLDDPage = (_NLDDPage = class NLDDPage extends LitElement {
|
|
|
38791
39444
|
const footer = this._footerEl;
|
|
38792
39445
|
if (!header || !footer) return;
|
|
38793
39446
|
const publish = () => {
|
|
38794
|
-
this.
|
|
38795
|
-
this.
|
|
39447
|
+
this._publish("--_header-height", `${this.stickyHeader ? header.offsetHeight : 0}px`);
|
|
39448
|
+
this._publish("--_footer-height", `${this.stickyFooter ? footer.offsetHeight : 0}px`);
|
|
39449
|
+
this._publish("--_header-full-height", `${this._measureHeaderFullHeight(header)}px`);
|
|
38796
39450
|
if (this._isRoot) this.style.removeProperty("--_scroll-height");
|
|
38797
|
-
else this.
|
|
39451
|
+
else this._publish("--_scroll-height", `${this._scrollEl?.clientHeight ?? 0}px`);
|
|
38798
39452
|
};
|
|
38799
39453
|
publish();
|
|
38800
39454
|
this._insetObserver = new ResizeObserver(publish);
|
|
@@ -38808,6 +39462,27 @@ var NLDDPage = (_NLDDPage = class NLDDPage extends LitElement {
|
|
|
38808
39462
|
this._insetObserver = null;
|
|
38809
39463
|
}
|
|
38810
39464
|
}
|
|
39465
|
+
/**
|
|
39466
|
+
* Writes a published variable only when it changes. These values size the
|
|
39467
|
+
* elements the observer watches, so a rewrite feeds the callback its own
|
|
39468
|
+
* next notification, which is what Chromium reports as "ResizeObserver
|
|
39469
|
+
* loop completed with undelivered notifications".
|
|
39470
|
+
*/
|
|
39471
|
+
_publish(name, value) {
|
|
39472
|
+
if (this.style.getPropertyValue(name) === value) return;
|
|
39473
|
+
this.style.setProperty(name, value);
|
|
39474
|
+
}
|
|
39475
|
+
/**
|
|
39476
|
+
* The header at its full height: the last measurement taken at scroll top.
|
|
39477
|
+
* A top title bar shrinks as you pass its anchor, and the space reserved for
|
|
39478
|
+
* it must not shrink along, or the content it starts under is dragged up
|
|
39479
|
+
* from under the cursor. Which mode reserves that space is the CSS's call.
|
|
39480
|
+
*/
|
|
39481
|
+
_measureHeaderFullHeight(header) {
|
|
39482
|
+
if (!this.stickyHeader) return 0;
|
|
39483
|
+
if (this.scrollTarget.scrollTop === 0) this._headerFullHeight = header.offsetHeight;
|
|
39484
|
+
return this._headerFullHeight;
|
|
39485
|
+
}
|
|
38811
39486
|
_setupScrollListener() {
|
|
38812
39487
|
const target = this.scrollEventTarget;
|
|
38813
39488
|
target.addEventListener("scroll", this._onScroll, { passive: true });
|
|
@@ -38819,23 +39494,6 @@ var NLDDPage = (_NLDDPage = class NLDDPage extends LitElement {
|
|
|
38819
39494
|
this._scrollTarget = null;
|
|
38820
39495
|
}
|
|
38821
39496
|
}
|
|
38822
|
-
_setupHeaderObserver() {
|
|
38823
|
-
this._teardownHeaderObserver();
|
|
38824
|
-
const header = this._headerEl;
|
|
38825
|
-
const scroll = this._scrollEl;
|
|
38826
|
-
if (!header || !scroll) return;
|
|
38827
|
-
this._headerObserver = new ResizeObserver(() => {
|
|
38828
|
-
if (scroll.scrollTop > 0) return;
|
|
38829
|
-
scroll.style.paddingTop = `${header.offsetHeight}px`;
|
|
38830
|
-
});
|
|
38831
|
-
this._headerObserver.observe(header);
|
|
38832
|
-
}
|
|
38833
|
-
_teardownHeaderObserver() {
|
|
38834
|
-
if (this._headerObserver) {
|
|
38835
|
-
this._headerObserver.disconnect();
|
|
38836
|
-
this._headerObserver = null;
|
|
38837
|
-
}
|
|
38838
|
-
}
|
|
38839
39497
|
_setupMainSlotListener() {
|
|
38840
39498
|
this._teardownMainSlotListener();
|
|
38841
39499
|
const slot = this.shadowRoot?.querySelector("slot:not([name])") ?? null;
|
|
@@ -41155,6 +41813,9 @@ function sheetTemplate(component) {
|
|
|
41155
41813
|
<div class="sheet__body">
|
|
41156
41814
|
<slot></slot>
|
|
41157
41815
|
</div>
|
|
41816
|
+
<!-- Inside the dialog, so it escapes the inertness a modal imposes on
|
|
41817
|
+
everything outside it. Plumbing, not consumer API. -->
|
|
41818
|
+
<slot name="notifications"></slot>
|
|
41158
41819
|
</dialog>
|
|
41159
41820
|
`;
|
|
41160
41821
|
}
|
|
@@ -42611,6 +43272,9 @@ function windowTemplate(component) {
|
|
|
42611
43272
|
<div class="window__body">
|
|
42612
43273
|
<slot></slot>
|
|
42613
43274
|
</div>
|
|
43275
|
+
<!-- Inside the dialog, so it escapes the inertness a modal imposes on
|
|
43276
|
+
everything outside it. Plumbing, not consumer API. -->
|
|
43277
|
+
<slot name="notifications"></slot>
|
|
42614
43278
|
</dialog>
|
|
42615
43279
|
`;
|
|
42616
43280
|
}
|
|
@@ -45997,8 +46661,10 @@ var NLDDMenuBar = (_NLDDMenuBar = class NLDDMenuBar extends withTranslations(Lit
|
|
|
45997
46661
|
}
|
|
45998
46662
|
/**
|
|
45999
46663
|
* Calculate which slotted items overflow and hide them behind an overflow button.
|
|
46000
|
-
*
|
|
46001
|
-
*
|
|
46664
|
+
* Tested both ways in menu-bar.test.ts: with real layout for the whole path, and
|
|
46665
|
+
* with mocked widths for the arithmetic. The space reserved for the button needs
|
|
46666
|
+
* the mocks — the button is a shrinkable flex item, so the rendered layout
|
|
46667
|
+
* absorbs whatever the arithmetic leaves over and always looks right.
|
|
46002
46668
|
*/
|
|
46003
46669
|
_updateOverflow() {
|
|
46004
46670
|
const overflowButton = this._overflowButton;
|
|
@@ -49992,6 +50658,9 @@ function modalDialogTemplate(component) {
|
|
|
49992
50658
|
<slot></slot>
|
|
49993
50659
|
<slot slot="actions" name="actions"></slot>
|
|
49994
50660
|
</nldd-inline-dialog>
|
|
50661
|
+
<!-- Inside the dialog, so it escapes the inertness a modal imposes on
|
|
50662
|
+
everything outside it. Plumbing, not consumer API. -->
|
|
50663
|
+
<slot name="notifications"></slot>
|
|
49995
50664
|
</dialog>
|
|
49996
50665
|
`;
|
|
49997
50666
|
}
|
|
@@ -50617,6 +51286,12 @@ var notificationStyles = css`
|
|
|
50617
51286
|
display: none;
|
|
50618
51287
|
}
|
|
50619
51288
|
|
|
51289
|
+
/* Off for good once it has arrived: a move between overlays reinserts the
|
|
51290
|
+
element, and the browser would play the arrival again. */
|
|
51291
|
+
:host([data-arrived]) {
|
|
51292
|
+
animation: none;
|
|
51293
|
+
}
|
|
51294
|
+
|
|
50620
51295
|
@media (prefers-reduced-motion: reduce) {
|
|
50621
51296
|
:host {
|
|
50622
51297
|
transition: none;
|
|
@@ -50793,11 +51468,12 @@ function notificationTemplate(component) {
|
|
|
50793
51468
|
* ## Opening it
|
|
50794
51469
|
* Under the front notification sits a strip as wide as the deck and as tall as
|
|
50795
51470
|
* the deck is when it fans out. That strip is the handle: pointing at it fans
|
|
50796
|
-
* the deck out to fill it, clicking it lays the deck out as a list.
|
|
50797
|
-
*
|
|
50798
|
-
* back.
|
|
50799
|
-
*
|
|
50800
|
-
*
|
|
51471
|
+
* the deck out to fill it, clicking it lays the deck out as a list. Tabbing
|
|
51472
|
+
* into the region does the same, and clicking outside it or tabbing away puts
|
|
51473
|
+
* it back. Dismissing one keeps the list open for the rest. Nothing counts down
|
|
51474
|
+
* while the list is open, because open means someone is reading. The
|
|
51475
|
+
* notification itself is not a button, so a click on the message you are
|
|
51476
|
+
* reading does nothing, which is what it says it does.
|
|
50801
51477
|
*/
|
|
50802
51478
|
var REGION_ID = "nldd-notification-region";
|
|
50803
51479
|
/** Past this a notification adds no edge of its own: it would sit exactly
|
|
@@ -50806,6 +51482,78 @@ var REGION_ID = "nldd-notification-region";
|
|
|
50806
51482
|
var MAX_DEPTH = 2;
|
|
50807
51483
|
var frontObserver = null;
|
|
50808
51484
|
var expanded = false;
|
|
51485
|
+
/**
|
|
51486
|
+
* A modal overlay paints in the browser's top layer, above the whole document
|
|
51487
|
+
* and out of reach of any z-index, and while one is open everything outside it
|
|
51488
|
+
* is inert: a region left on the body is not merely behind the sheet, it is
|
|
51489
|
+
* unclickable even once you put it in the top layer yourself. So the region
|
|
51490
|
+
* travels. It lives in the topmost open overlay and sinks back as they close,
|
|
51491
|
+
* which is also what makes a notification raised before the sheet opened come
|
|
51492
|
+
* along rather than stay behind it.
|
|
51493
|
+
*
|
|
51494
|
+
* Innermost last. Entries are verified on read rather than trusted, so an
|
|
51495
|
+
* overlay torn out of the DOM without closing cannot strand the region.
|
|
51496
|
+
*/
|
|
51497
|
+
var overlays = [];
|
|
51498
|
+
var overlayWatch = null;
|
|
51499
|
+
/** Open as a modal, and with a slot to take the region in. A modal without one
|
|
51500
|
+
* would swallow the region, which is worse than leaving it behind. */
|
|
51501
|
+
function isOpenOverlay(el) {
|
|
51502
|
+
const root = el.shadowRoot;
|
|
51503
|
+
if (!root?.querySelector("slot[name=\"notifications\"]")) return false;
|
|
51504
|
+
return root.querySelector("dialog")?.matches(":modal") ?? false;
|
|
51505
|
+
}
|
|
51506
|
+
function regionHost() {
|
|
51507
|
+
while (overlays.length) {
|
|
51508
|
+
const top = overlays[overlays.length - 1];
|
|
51509
|
+
if (top.isConnected && isOpenOverlay(top)) return top;
|
|
51510
|
+
overlays.pop();
|
|
51511
|
+
}
|
|
51512
|
+
return document.body;
|
|
51513
|
+
}
|
|
51514
|
+
function placeRegion(region) {
|
|
51515
|
+
const host = regionHost();
|
|
51516
|
+
if (region.parentElement === host) return;
|
|
51517
|
+
const inOverlay = host !== document.body;
|
|
51518
|
+
if (inOverlay) region.setAttribute("slot", "notifications");
|
|
51519
|
+
else region.removeAttribute("slot");
|
|
51520
|
+
if (inOverlay) region.setAttribute("popover", "manual");
|
|
51521
|
+
else region.removeAttribute("popover");
|
|
51522
|
+
const carried = notifications(region);
|
|
51523
|
+
carried.forEach((item) => item._setMoving?.(true));
|
|
51524
|
+
host.appendChild(region);
|
|
51525
|
+
if (inOverlay) region.showPopover();
|
|
51526
|
+
carried.forEach((item) => item._setMoving?.(false));
|
|
51527
|
+
}
|
|
51528
|
+
function relocateRegion() {
|
|
51529
|
+
const region = document.getElementById(REGION_ID);
|
|
51530
|
+
if (region) placeRegion(region);
|
|
51531
|
+
}
|
|
51532
|
+
function watchOverlays() {
|
|
51533
|
+
if (overlayWatch) return;
|
|
51534
|
+
overlayWatch = new AbortController();
|
|
51535
|
+
const { signal } = overlayWatch;
|
|
51536
|
+
document.querySelectorAll("*").forEach((el) => {
|
|
51537
|
+
if (isOpenOverlay(el)) overlays.push(el);
|
|
51538
|
+
});
|
|
51539
|
+
document.addEventListener("open", (event) => {
|
|
51540
|
+
const host = event.target;
|
|
51541
|
+
if (!host || !isOpenOverlay(host)) return;
|
|
51542
|
+
if (!overlays.includes(host)) overlays.push(host);
|
|
51543
|
+
relocateRegion();
|
|
51544
|
+
}, { signal });
|
|
51545
|
+
document.addEventListener("close", (event) => {
|
|
51546
|
+
const host = event.target;
|
|
51547
|
+
const at = host ? overlays.indexOf(host) : -1;
|
|
51548
|
+
if (at === -1) return;
|
|
51549
|
+
overlays.splice(at, 1);
|
|
51550
|
+
relocateRegion();
|
|
51551
|
+
}, {
|
|
51552
|
+
capture: true,
|
|
51553
|
+
signal
|
|
51554
|
+
});
|
|
51555
|
+
}
|
|
51556
|
+
if (typeof document !== "undefined") watchOverlays();
|
|
50809
51557
|
function notifications(region) {
|
|
50810
51558
|
return Array.from(region.querySelectorAll(":scope > nldd-notification"));
|
|
50811
51559
|
}
|
|
@@ -50823,22 +51571,33 @@ function ensureRegion(label) {
|
|
|
50823
51571
|
"z-index: 1000",
|
|
50824
51572
|
"top: max(var(--semantics-overlays-inset), env(safe-area-inset-top))",
|
|
50825
51573
|
"right: max(var(--semantics-overlays-inset), env(safe-area-inset-right))",
|
|
51574
|
+
"bottom: auto",
|
|
50826
51575
|
"left: auto",
|
|
50827
51576
|
"display: grid",
|
|
51577
|
+
"align-content: start",
|
|
50828
51578
|
"align-items: start",
|
|
51579
|
+
"margin: 0",
|
|
51580
|
+
"border: none",
|
|
51581
|
+
"background: none",
|
|
50829
51582
|
"pointer-events: none",
|
|
50830
|
-
"max-height: calc(100dvh - 2 * var(--semantics-overlays-inset))"
|
|
51583
|
+
"max-height: calc(100dvh - 2 * var(--semantics-overlays-inset))",
|
|
51584
|
+
"overflow: visible",
|
|
51585
|
+
"padding: 0",
|
|
51586
|
+
"color: inherit"
|
|
50831
51587
|
].join(";");
|
|
50832
51588
|
region.addEventListener("pointerdown", () => {}, { passive: true });
|
|
50833
|
-
region.addEventListener("focusin", () =>
|
|
51589
|
+
region.addEventListener("focusin", () => {
|
|
51590
|
+
if (isKeyboardMode()) setExpanded(region, true);
|
|
51591
|
+
});
|
|
50834
51592
|
region.addEventListener("focusout", (e) => {
|
|
50835
|
-
|
|
51593
|
+
const next = e.relatedTarget;
|
|
51594
|
+
if (!next || !isKeyboardMode() || region.contains(next)) return;
|
|
50836
51595
|
setExpanded(region, false);
|
|
50837
51596
|
});
|
|
50838
51597
|
region.setAttribute("role", "region");
|
|
50839
51598
|
region.setAttribute("aria-label", label);
|
|
50840
51599
|
region.appendChild(makeExpander(region));
|
|
50841
|
-
|
|
51600
|
+
placeRegion(region);
|
|
50842
51601
|
return region;
|
|
50843
51602
|
}
|
|
50844
51603
|
/**
|
|
@@ -50846,19 +51605,22 @@ function ensureRegion(label) {
|
|
|
50846
51605
|
* behind it, which on their own are a few pixels tall and no target at all, and
|
|
50847
51606
|
* it stays exactly as tall as the deck is when it fans out, so what you point
|
|
50848
51607
|
* at is what you get.
|
|
51608
|
+
*
|
|
51609
|
+
* A second row of the region's grid rather than hung below the region, so it
|
|
51610
|
+
* follows the deck and not the region's edge. A region taller than its deck,
|
|
51611
|
+
* as Safari made it inside an overlay while the rows still stretched, would
|
|
51612
|
+
* otherwise put the strip far from the deck it opens.
|
|
50849
51613
|
*/
|
|
50850
51614
|
function makeExpander(region) {
|
|
50851
51615
|
const strip = document.createElement("div");
|
|
50852
51616
|
strip.dataset.expander = "";
|
|
50853
51617
|
strip.style.cssText = [
|
|
50854
|
-
"position: absolute",
|
|
50855
|
-
"z-index: 1000",
|
|
50856
|
-
"top: 100%",
|
|
50857
|
-
"right: 0",
|
|
50858
|
-
"left: 0",
|
|
50859
51618
|
"display: none",
|
|
50860
|
-
"
|
|
50861
|
-
"
|
|
51619
|
+
"position: relative",
|
|
51620
|
+
"grid-area: 2 / 1",
|
|
51621
|
+
"z-index: 1000",
|
|
51622
|
+
"pointer-events: auto",
|
|
51623
|
+
"height: var(--primitives-space-24)"
|
|
50862
51624
|
].join(";");
|
|
50863
51625
|
strip.addEventListener("pointerenter", () => setFanned(region, true));
|
|
50864
51626
|
strip.addEventListener("pointerleave", () => setFanned(region, false));
|
|
@@ -50868,7 +51630,9 @@ function makeExpander(region) {
|
|
|
50868
51630
|
/** Newest in front: what just happened is what you want to read, and the older
|
|
50869
51631
|
* ones slide back and downwards behind it. */
|
|
50870
51632
|
function joinRegion(notification, label) {
|
|
51633
|
+
watchOverlays();
|
|
50871
51634
|
const region = ensureRegion(label);
|
|
51635
|
+
placeRegion(region);
|
|
50872
51636
|
notification.style.pointerEvents = "auto";
|
|
50873
51637
|
region.prepend(notification);
|
|
50874
51638
|
syncStack(region);
|
|
@@ -50936,10 +51700,10 @@ function applyOverflow(region) {
|
|
|
50936
51700
|
function setScrolling(region, on) {
|
|
50937
51701
|
const inset = "var(--semantics-overlays-inset)";
|
|
50938
51702
|
const shadow = "var(--primitives-space-48)";
|
|
50939
|
-
region.style.overflow = on ? "auto" : "";
|
|
51703
|
+
region.style.overflow = on ? "auto" : "visible";
|
|
50940
51704
|
region.style.overscrollBehavior = on ? "contain" : "";
|
|
50941
|
-
region.style.padding = on ? `${inset} ${inset} ${shadow} ${shadow}` : "";
|
|
50942
|
-
region.style.margin = on ? `calc(-1 * ${inset}) calc(-1 * ${inset}) 0 calc(-1 * ${shadow})` : "";
|
|
51705
|
+
region.style.padding = on ? `${inset} ${inset} ${shadow} ${shadow}` : "0";
|
|
51706
|
+
region.style.margin = on ? `calc(-1 * ${inset}) calc(-1 * ${inset}) 0 calc(-1 * ${shadow})` : "0";
|
|
50943
51707
|
region.style.maxHeight = on ? "100dvh" : `calc(100dvh - 2 * ${inset})`;
|
|
50944
51708
|
}
|
|
50945
51709
|
function setExpanded(region, next) {
|
|
@@ -51003,6 +51767,13 @@ var nlddNotificationTranslations = {
|
|
|
51003
51767
|
* Nothing about the position is settable, so notifications from anywhere in an
|
|
51004
51768
|
* application land in the same place and stack in the same order.
|
|
51005
51769
|
*
|
|
51770
|
+
* A modal overlay is the one thing that moves it. An nldd-sheet, nldd-window or
|
|
51771
|
+
* nldd-modal-dialog paints in the browser's top layer, above the whole page and
|
|
51772
|
+
* out of reach of any z-index, and while one is open everything outside it is
|
|
51773
|
+
* inert. So the region goes in: into the topmost overlay that is open, and back
|
|
51774
|
+
* down as they close. A notification already on screen travels with it, and one
|
|
51775
|
+
* raised from inside a sheet is readable and reachable where it is raised.
|
|
51776
|
+
*
|
|
51006
51777
|
* ## The stack
|
|
51007
51778
|
* More than one is a deck, not a list: the front one is readable and the older
|
|
51008
51779
|
* ones peek out below it, so a burst of messages takes the room of roughly one.
|
|
@@ -51087,6 +51858,10 @@ var NLDDNotification = (_NLDDNotification = class NLDDNotification extends withT
|
|
|
51087
51858
|
this._pointerInside = false;
|
|
51088
51859
|
this._resumeTimer();
|
|
51089
51860
|
};
|
|
51861
|
+
this._onAnimationEnd = (e) => {
|
|
51862
|
+
if (e.target !== this || e.animationName !== "notification-arrive") return;
|
|
51863
|
+
this.toggleAttribute("data-arrived", true);
|
|
51864
|
+
};
|
|
51090
51865
|
this._onFocusIn = () => {
|
|
51091
51866
|
this._focusInside = true;
|
|
51092
51867
|
this._clearTimer();
|
|
@@ -51107,6 +51882,7 @@ var NLDDNotification = (_NLDDNotification = class NLDDNotification extends withT
|
|
|
51107
51882
|
};
|
|
51108
51883
|
this._onKeyDown = (e) => {
|
|
51109
51884
|
if (e.key !== "Escape") return;
|
|
51885
|
+
e.preventDefault();
|
|
51110
51886
|
e.stopPropagation();
|
|
51111
51887
|
this._dismiss();
|
|
51112
51888
|
};
|
|
@@ -51130,6 +51906,7 @@ var NLDDNotification = (_NLDDNotification = class NLDDNotification extends withT
|
|
|
51130
51906
|
this.addEventListener("focusin", this._onFocusIn);
|
|
51131
51907
|
this.addEventListener("focusout", this._onFocusOut);
|
|
51132
51908
|
this.addEventListener("keydown", this._onKeyDown);
|
|
51909
|
+
this.addEventListener("animationend", this._onAnimationEnd);
|
|
51133
51910
|
queueMicrotask(() => {
|
|
51134
51911
|
if (!this.isConnected || this._moving) return;
|
|
51135
51912
|
const region = document.getElementById("nldd-notification-region");
|
|
@@ -51148,6 +51925,8 @@ var NLDDNotification = (_NLDDNotification = class NLDDNotification extends withT
|
|
|
51148
51925
|
this.removeEventListener("focusin", this._onFocusIn);
|
|
51149
51926
|
this.removeEventListener("focusout", this._onFocusOut);
|
|
51150
51927
|
this.removeEventListener("keydown", this._onKeyDown);
|
|
51928
|
+
this.removeEventListener("animationend", this._onAnimationEnd);
|
|
51929
|
+
this.removeAttribute("data-arrived");
|
|
51151
51930
|
leaveRegion(this);
|
|
51152
51931
|
}
|
|
51153
51932
|
willUpdate(changed) {
|
|
@@ -51155,6 +51934,16 @@ var NLDDNotification = (_NLDDNotification = class NLDDNotification extends withT
|
|
|
51155
51934
|
if (changed.has("variant")) this.setAttribute("role", this.variant === "critical" ? "alert" : "status");
|
|
51156
51935
|
if (changed.has("duration")) this._remainingDuration = this.duration;
|
|
51157
51936
|
}
|
|
51937
|
+
/** Called by the region while it carries this notification to another
|
|
51938
|
+
* parent. A move disconnects the element, and without this the
|
|
51939
|
+
* notification would read its own disconnect as having been dismissed.
|
|
51940
|
+
* It also counts as having arrived: the browser starts a CSS animation over
|
|
51941
|
+
* when its element comes back, and one caught halfway in would slide in
|
|
51942
|
+
* from off the screen a second time. */
|
|
51943
|
+
_setMoving(moving) {
|
|
51944
|
+
this._moving = moving;
|
|
51945
|
+
if (moving) this.toggleAttribute("data-arrived", true);
|
|
51946
|
+
}
|
|
51158
51947
|
/** Called by the region. Starting the clock here rather than on connect is
|
|
51159
51948
|
* the whole point: a notification three deep in the deck is not counting. */
|
|
51160
51949
|
_setFront(isFront) {
|