@sb1/indeks-web 0.24.0 → 0.26.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +31 -0
- package/dist/npm/index.js +111 -195
- package/dist/npm/lib/components/combobox/IxCombobox.d.ts +1 -1
- package/dist/npm/lib/components/phone-number-field/IxPhoneNumberField.d.ts +1 -0
- package/dist/npm/lib/components/progress-bar/IxProgressBar.d.ts +11 -10
- package/dist/npm/lib/components/radio-group/IxRadioGroup.d.ts +18 -49
- package/dist/npm/lib/components/tabs/IxTabs.d.ts +17 -55
- package/package.json +6 -6
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,36 @@
|
|
|
1
1
|
# @sb1/indeks-web
|
|
2
2
|
|
|
3
|
+
## 0.26.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 250261e: Én hook per del og ingen `:where()` i accordion, read-more og tabs (ADR-DS-009).
|
|
8
|
+
|
|
9
|
+
Breaking: Accordion styler `<details>` og `<summary>` direkte, og innholdet må ha `ix-accordion__content`. Tabs styler `<ix-tab-list>`, `<ix-tab>` og `<ix-tab-panel>` inni `.ix-tabs`. `ix-accordion__item`, `ix-accordion__header`, `ix-tabs__list`, `ix-tabs__tab` og `ix-tabs__panel` styles ikke lenger, og React setter dem ikke.
|
|
10
|
+
- 140b0cb: Én hook per del og ingen `:where()` i checkbox, checkbox-group, chip og modal (ADR-DS-009).
|
|
11
|
+
|
|
12
|
+
Breaking: `ix-checkbox--disabled` er fjernet. Bruk `disabled` på inputen. `ix-checkbox__input` og `ix-checkbox__label` styles ikke lenger, men gjør ingen skade i markupen.
|
|
13
|
+
- 04e6c71: ProgressBar viser nå status som i Figma. I `success` og `error` ser støtteteksten under baren ut som en ValidationMessage, med statusikonet foran. Ikonet vises også uten støttetekst.
|
|
14
|
+
|
|
15
|
+
**Breaking:** Statusikonet er flyttet fra headeren til støtteteksten, og feilikonet heter nå `close` i stedet for `priority_high`. Har du stylet `.ix-progress-bar__header ix-icon`, eller lett etter ikonet med navn, må du bruke `.ix-progress-bar__support ix-icon[data-badge]`. Meldingen bruker ikke `data-field="error"`, så et `<ix-field>` rundt en ProgressBar tar den ikke for sin egen feilmelding.
|
|
16
|
+
- 636e79c: Én hook per del og ingen `:where()` i radio-group (ADR-DS-009).
|
|
17
|
+
|
|
18
|
+
Breaking: `ix-radio-button`, `ix-radio-button__input` og `ix-radio-button__label` er fjernet. Radio styles bare inni `ix-radio-group` eller `.ix-radio-group`, og klassene gjør ingen skade i markupen. `ix-radio-group` finner labelen i inputens forelder.
|
|
19
|
+
- 04e6c71: ValidationMessage har fått en suksessvariant: `data-status="success"` i HTML og `status="success"` i React gir grønn tekst og en sirkel med hake. `<ix-field>`, `<ix-checkbox-group>`, `<ix-radio-group>` og `<ix-phone-number-field>` setter ikke `aria-invalid` for en suksessmelding, og `<ix-phone-number-field>` setter heller ikke `data-state="error"`.
|
|
20
|
+
|
|
21
|
+
Feilmeldingen i `<ix-phone-number-field>` har nå samme avstand mellom ikon og tekst som i de andre skjemafeltene (`xs` i stedet for `2xs`).
|
|
22
|
+
|
|
23
|
+
### Patch Changes
|
|
24
|
+
|
|
25
|
+
- 329f308: Én hook per del og ingen `:where()` i combobox og date-field (ADR-DS-009).
|
|
26
|
+
|
|
27
|
+
Breaking: chips-wrapperen i combobox styles fra `data-field="chips"`, ikke `ix-combobox__chips`. Toggle, listbox og option styles bare fra BEM-klassene, ikke fra `role` eller `.ix-text-field > button`. `ix-combobox` finner inputen med `.ix-text-field > input`.
|
|
28
|
+
- dc9139a: Modal: scroll-låsen på `<body>` slippes nå også når en åpen modal fjernes fra DOM-en, for eksempel når forelderen fjerner den i `onOpenChange` eller ved ruteskifte. Før ble siden stående uten scroll.
|
|
29
|
+
|
|
30
|
+
## 0.25.0
|
|
31
|
+
|
|
32
|
+
No changes in this release.
|
|
33
|
+
|
|
3
34
|
## 0.24.0
|
|
4
35
|
|
|
5
36
|
No changes in this release.
|
package/dist/npm/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
|
|
1
|
+
// \0@oxc-project+runtime@0.155.0/helpers/esm/typeof.js
|
|
2
2
|
function _typeof(o) {
|
|
3
3
|
"@babel/helpers - typeof";
|
|
4
4
|
return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function(o) {
|
|
@@ -7,8 +7,7 @@ function _typeof(o) {
|
|
|
7
7
|
return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o;
|
|
8
8
|
}, _typeof(o);
|
|
9
9
|
}
|
|
10
|
-
|
|
11
|
-
//#region \0@oxc-project+runtime@0.149.0/helpers/esm/toPrimitive.js
|
|
10
|
+
// \0@oxc-project+runtime@0.155.0/helpers/esm/toPrimitive.js
|
|
12
11
|
function toPrimitive(t, r) {
|
|
13
12
|
if ("object" != _typeof(t) || !t) return t;
|
|
14
13
|
var e = t[Symbol.toPrimitive];
|
|
@@ -19,14 +18,12 @@ function toPrimitive(t, r) {
|
|
|
19
18
|
}
|
|
20
19
|
return ("string" === r ? String : Number)(t);
|
|
21
20
|
}
|
|
22
|
-
|
|
23
|
-
//#region \0@oxc-project+runtime@0.149.0/helpers/esm/toPropertyKey.js
|
|
21
|
+
// \0@oxc-project+runtime@0.155.0/helpers/esm/toPropertyKey.js
|
|
24
22
|
function toPropertyKey(t) {
|
|
25
23
|
var i = toPrimitive(t, "string");
|
|
26
24
|
return "symbol" == _typeof(i) ? i : i + "";
|
|
27
25
|
}
|
|
28
|
-
|
|
29
|
-
//#region \0@oxc-project+runtime@0.149.0/helpers/esm/defineProperty.js
|
|
26
|
+
// \0@oxc-project+runtime@0.155.0/helpers/esm/defineProperty.js
|
|
30
27
|
function _defineProperty(e, r, t) {
|
|
31
28
|
return (r = toPropertyKey(r)) in e ? Object.defineProperty(e, r, {
|
|
32
29
|
value: t,
|
|
@@ -35,8 +32,7 @@ function _defineProperty(e, r, t) {
|
|
|
35
32
|
writable: !0
|
|
36
33
|
}) : e[r] = t, e;
|
|
37
34
|
}
|
|
38
|
-
|
|
39
|
-
//#region lib/components/checkbox-group/IxCheckboxGroup.ts
|
|
35
|
+
// lib/components/checkbox-group/IxCheckboxGroup.ts
|
|
40
36
|
/**
|
|
41
37
|
* IxCheckboxGroup — ARIA-lim for checkbox-grupper.
|
|
42
38
|
*
|
|
@@ -163,7 +159,8 @@ var IxCheckboxGroup = class extends HTMLElement {
|
|
|
163
159
|
this._errorObserver.observe(error, {
|
|
164
160
|
childList: true,
|
|
165
161
|
characterData: true,
|
|
166
|
-
subtree: true
|
|
162
|
+
subtree: true,
|
|
163
|
+
attributeFilter: ["data-status"]
|
|
167
164
|
});
|
|
168
165
|
this._syncInvalid(error);
|
|
169
166
|
}
|
|
@@ -198,7 +195,7 @@ var IxCheckboxGroup = class extends HTMLElement {
|
|
|
198
195
|
if (!input.id) input.id = `ix-checkbox-${++checkboxInputCounter}`;
|
|
199
196
|
const wrapper = input.closest(".ix-checkbox") ?? input.parentElement;
|
|
200
197
|
if (wrapper) {
|
|
201
|
-
const label = wrapper.querySelector("label
|
|
198
|
+
const label = wrapper.querySelector("label");
|
|
202
199
|
if (label instanceof HTMLLabelElement && !label.htmlFor) label.htmlFor = input.id;
|
|
203
200
|
}
|
|
204
201
|
}
|
|
@@ -207,7 +204,7 @@ var IxCheckboxGroup = class extends HTMLElement {
|
|
|
207
204
|
this._syncHostStateToInputs();
|
|
208
205
|
}
|
|
209
206
|
_syncInvalid(error) {
|
|
210
|
-
if (!!error.textContent?.trim()) this.setAttribute("aria-invalid", "true");
|
|
207
|
+
if (!!error.textContent?.trim() && error.dataset.status !== "success") this.setAttribute("aria-invalid", "true");
|
|
211
208
|
else this.removeAttribute("aria-invalid");
|
|
212
209
|
}
|
|
213
210
|
_syncHostStateToInputs() {
|
|
@@ -222,8 +219,7 @@ var IxCheckboxGroup = class extends HTMLElement {
|
|
|
222
219
|
}
|
|
223
220
|
}
|
|
224
221
|
};
|
|
225
|
-
|
|
226
|
-
//#region lib/components/combobox/IxCombobox.ts
|
|
222
|
+
// lib/components/combobox/IxCombobox.ts
|
|
227
223
|
/**
|
|
228
224
|
* IxCombobox — ARIA-lim for en søkbar select med single- og multiselect.
|
|
229
225
|
*
|
|
@@ -269,7 +265,7 @@ var IxCheckboxGroup = class extends HTMLElement {
|
|
|
269
265
|
* @example Single
|
|
270
266
|
* <ix-combobox data-no-hits-text="Ingen treff">
|
|
271
267
|
* <div class="ix-text-field">
|
|
272
|
-
* <input
|
|
268
|
+
* <input placeholder="Søk …" />
|
|
273
269
|
* <button class="ix-combobox__toggle"></button>
|
|
274
270
|
* </div>
|
|
275
271
|
* <div class="ix-combobox__listbox">
|
|
@@ -390,7 +386,7 @@ var IxCombobox = class extends HTMLElement {
|
|
|
390
386
|
return this.hasAttribute("disabled") || this.hasAttribute("readonly");
|
|
391
387
|
}
|
|
392
388
|
_wire() {
|
|
393
|
-
this._input = this.querySelector(".ix-text-field
|
|
389
|
+
this._input = this.querySelector(".ix-text-field > input");
|
|
394
390
|
this._listbox = this.querySelector(".ix-combobox__listbox");
|
|
395
391
|
this._toggle = this.querySelector(".ix-combobox__toggle");
|
|
396
392
|
this._chips = this.querySelector("[data-field=\"chips\"]");
|
|
@@ -867,8 +863,7 @@ var IxCombobox = class extends HTMLElement {
|
|
|
867
863
|
if (this._results) this._results.textContent = "";
|
|
868
864
|
}
|
|
869
865
|
};
|
|
870
|
-
|
|
871
|
-
//#region lib/components/field/formats.ts
|
|
866
|
+
// lib/components/field/formats.ts
|
|
872
867
|
/**
|
|
873
868
|
* Slot-definisjoner for pattern-strenger. Et pattern består av slot-tegn som
|
|
874
869
|
* brukeren fyller, og faste separatorer (alt annet) som settes inn automatisk.
|
|
@@ -1109,8 +1104,7 @@ registerFormat("amount", {
|
|
|
1109
1104
|
}),
|
|
1110
1105
|
live: true
|
|
1111
1106
|
});
|
|
1112
|
-
|
|
1113
|
-
//#region lib/components/date-field/IxDateField.ts
|
|
1107
|
+
// lib/components/date-field/IxDateField.ts
|
|
1114
1108
|
/**
|
|
1115
1109
|
* IxDateField — datofelt: formatert tekst-input (dd.mm.åååå) + native OS-kalender.
|
|
1116
1110
|
*
|
|
@@ -1319,8 +1313,7 @@ var IxDateField = class extends HTMLElement {
|
|
|
1319
1313
|
}
|
|
1320
1314
|
}
|
|
1321
1315
|
};
|
|
1322
|
-
|
|
1323
|
-
//#region lib/components/dropdown/IxDropdown.ts
|
|
1316
|
+
// lib/components/dropdown/IxDropdown.ts
|
|
1324
1317
|
/**
|
|
1325
1318
|
* IxDropdown — ARIA-lim for en nedtrekksmeny med handlinger eller navigasjon.
|
|
1326
1319
|
*
|
|
@@ -1816,8 +1809,7 @@ var IxDropdown = class extends HTMLElement {
|
|
|
1816
1809
|
}
|
|
1817
1810
|
}
|
|
1818
1811
|
};
|
|
1819
|
-
|
|
1820
|
-
//#region lib/components/field/IxField.ts
|
|
1812
|
+
// lib/components/field/IxField.ts
|
|
1821
1813
|
/**
|
|
1822
1814
|
* IxField — tilgjengelighetslim mellom label, input og hjelpetekster.
|
|
1823
1815
|
*
|
|
@@ -2030,7 +2022,8 @@ var IxField = class extends HTMLElement {
|
|
|
2030
2022
|
this._observer.observe(error, {
|
|
2031
2023
|
childList: true,
|
|
2032
2024
|
characterData: true,
|
|
2033
|
-
subtree: true
|
|
2025
|
+
subtree: true,
|
|
2026
|
+
attributeFilter: ["data-status"]
|
|
2034
2027
|
});
|
|
2035
2028
|
this._syncInvalid(control, error);
|
|
2036
2029
|
}
|
|
@@ -2197,7 +2190,7 @@ var IxField = class extends HTMLElement {
|
|
|
2197
2190
|
this._formatTeardown = null;
|
|
2198
2191
|
}
|
|
2199
2192
|
_syncInvalid(control, error) {
|
|
2200
|
-
const hasError = !!error.textContent?.trim();
|
|
2193
|
+
const hasError = !!error.textContent?.trim() && error.dataset.status !== "success";
|
|
2201
2194
|
if (hasError) control.setAttribute("aria-invalid", "true");
|
|
2202
2195
|
else control.removeAttribute("aria-invalid");
|
|
2203
2196
|
control.closest(".ix-dropdown")?.toggleAttribute("data-invalid", hasError);
|
|
@@ -2245,8 +2238,7 @@ var IxField = class extends HTMLElement {
|
|
|
2245
2238
|
}
|
|
2246
2239
|
};
|
|
2247
2240
|
_defineProperty(IxField, "registerFormatter", registerFormat);
|
|
2248
|
-
|
|
2249
|
-
//#region lib/components/icon/IxIcon.ts
|
|
2241
|
+
// lib/components/icon/IxIcon.ts
|
|
2250
2242
|
var ICON_CDN_BASE = "https://cdn.sparebank1.no/icons";
|
|
2251
2243
|
var COMMON_ICON_NAMES = [
|
|
2252
2244
|
"close",
|
|
@@ -2432,8 +2424,7 @@ var IxIcon = class extends HTMLElement {
|
|
|
2432
2424
|
}
|
|
2433
2425
|
}
|
|
2434
2426
|
};
|
|
2435
|
-
|
|
2436
|
-
//#region lib/components/phone-number-field/countries.ts
|
|
2427
|
+
// lib/components/phone-number-field/countries.ts
|
|
2437
2428
|
var NORDIC = /* @__PURE__ */ new Set([
|
|
2438
2429
|
"SE",
|
|
2439
2430
|
"DK",
|
|
@@ -2758,8 +2749,7 @@ function getDefaultCountries(locale = "nb") {
|
|
|
2758
2749
|
function normalizeLocale(value) {
|
|
2759
2750
|
return value === "nn" || value === "en" ? value : "nb";
|
|
2760
2751
|
}
|
|
2761
|
-
|
|
2762
|
-
//#region lib/components/phone-number-field/IxPhoneNumberField.ts
|
|
2752
|
+
// lib/components/phone-number-field/IxPhoneNumberField.ts
|
|
2763
2753
|
/**
|
|
2764
2754
|
* IxPhoneNumberField — telefonnummer med landvelger.
|
|
2765
2755
|
*
|
|
@@ -2827,7 +2817,7 @@ function normalizeLocale(value) {
|
|
|
2827
2817
|
* <div data-field="items">
|
|
2828
2818
|
* <ix-combobox data-field="country" class="ix-combobox" data-no-hits-text="Ingen treff">
|
|
2829
2819
|
* <div class="ix-text-field">
|
|
2830
|
-
* <input
|
|
2820
|
+
* <input aria-label="Landkode" />
|
|
2831
2821
|
* <button type="button" class="ix-combobox__toggle" aria-label="Vis land"></button>
|
|
2832
2822
|
* </div>
|
|
2833
2823
|
* <div class="ix-combobox__listbox" hidden></div>
|
|
@@ -2929,7 +2919,8 @@ var IxPhoneNumberField = class extends HTMLElement {
|
|
|
2929
2919
|
this._errorObserver.observe(error, {
|
|
2930
2920
|
childList: true,
|
|
2931
2921
|
characterData: true,
|
|
2932
|
-
subtree: true
|
|
2922
|
+
subtree: true,
|
|
2923
|
+
attributeFilter: ["data-status"]
|
|
2933
2924
|
});
|
|
2934
2925
|
this._syncInvalid(error);
|
|
2935
2926
|
} else this.removeAttribute("aria-invalid");
|
|
@@ -2958,13 +2949,17 @@ var IxPhoneNumberField = class extends HTMLElement {
|
|
|
2958
2949
|
const combobox = this._combobox();
|
|
2959
2950
|
return Array.from(this.querySelectorAll("input")).find((input) => !combobox?.contains(input)) ?? null;
|
|
2960
2951
|
}
|
|
2952
|
+
_hasError(error) {
|
|
2953
|
+
return !!error?.textContent?.trim() && error.dataset.status !== "success";
|
|
2954
|
+
}
|
|
2961
2955
|
_syncInvalid(error) {
|
|
2962
|
-
if (
|
|
2956
|
+
if (this._hasError(error)) this.setAttribute("aria-invalid", "true");
|
|
2963
2957
|
else this.removeAttribute("aria-invalid");
|
|
2964
2958
|
this._syncDataState();
|
|
2965
2959
|
}
|
|
2966
2960
|
_syncDataState() {
|
|
2967
|
-
const
|
|
2961
|
+
const error = this.querySelector(":scope > [data-field=\"error\"]");
|
|
2962
|
+
const state = this._hasError(error) ? "error" : this.hasAttribute("readonly") ? "readonly" : this.hasAttribute("disabled") ? "disabled" : null;
|
|
2968
2963
|
if (state) this.setAttribute("data-state", state);
|
|
2969
2964
|
else this.removeAttribute("data-state");
|
|
2970
2965
|
}
|
|
@@ -3096,8 +3091,7 @@ var IxPhoneNumberField = class extends HTMLElement {
|
|
|
3096
3091
|
})) if (!input.hasAttribute(attr)) input.setAttribute(attr, value);
|
|
3097
3092
|
}
|
|
3098
3093
|
};
|
|
3099
|
-
|
|
3100
|
-
//#region lib/components/popover/IxPopover.ts
|
|
3094
|
+
// lib/components/popover/IxPopover.ts
|
|
3101
3095
|
/**
|
|
3102
3096
|
* IxPopover — ARIA-lim for en popover med kontekstuell informasjon eller handlinger.
|
|
3103
3097
|
*
|
|
@@ -3407,8 +3401,7 @@ var IxPopover = class extends HTMLElement {
|
|
|
3407
3401
|
}
|
|
3408
3402
|
}
|
|
3409
3403
|
};
|
|
3410
|
-
|
|
3411
|
-
//#region lib/utils/platform.ts
|
|
3404
|
+
// lib/utils/platform.ts
|
|
3412
3405
|
/**
|
|
3413
3406
|
* Plattform-deteksjon for web components.
|
|
3414
3407
|
*
|
|
@@ -3427,13 +3420,12 @@ function isIOS() {
|
|
|
3427
3420
|
if (typeof navigator === "undefined") return false;
|
|
3428
3421
|
return /iPad|iPhone|iPod/.test(navigator.userAgent);
|
|
3429
3422
|
}
|
|
3430
|
-
|
|
3431
|
-
//#region lib/components/progress-bar/IxProgressBar.ts
|
|
3423
|
+
// lib/components/progress-bar/IxProgressBar.ts
|
|
3432
3424
|
/**
|
|
3433
3425
|
* IxProgressBar — viser fremdrift i én sammenhengende prosess.
|
|
3434
3426
|
*
|
|
3435
3427
|
* DOM-generator: web componenten eier og genererer all indre HTML (header med
|
|
3436
|
-
* label + verdi
|
|
3428
|
+
* label + verdi, track + fill, og en støttetekst-region).
|
|
3437
3429
|
* Forfatteren skriver kun host-elementet `<ix-progress-bar>` med attributter —
|
|
3438
3430
|
* ingen intern struktur.
|
|
3439
3431
|
*
|
|
@@ -3441,9 +3433,11 @@ function isIOS() {
|
|
|
3441
3433
|
* - `active` (default): role="progressbar" med aria-valuenow/min/max på host.
|
|
3442
3434
|
* Nøytral/primær fremdriftsfarge. Ingen statusikon. Kan vise prosent.
|
|
3443
3435
|
* - `success` / `error`: rollen `progressbar` FJERNES (prosessen er ferdig/avbrutt).
|
|
3444
|
-
*
|
|
3445
|
-
*
|
|
3446
|
-
*
|
|
3436
|
+
* `data-status` (success/danger) kobler --ix-color-status-* til fyllet og
|
|
3437
|
+
* til støtteteksten, som får badge-ikon foran og ser ut som en
|
|
3438
|
+
* ValidationMessage, slik Figma viser. Den bruker ikke `data-field="error"`:
|
|
3439
|
+
* ix-field og gruppene leter etter det i hele subtreet og ville tatt
|
|
3440
|
+
* meldingen for feltets feilmelding. Støtteteksten er en aria-live-region
|
|
3447
3441
|
* (role="status") så overgangen annonseres politely.
|
|
3448
3442
|
*
|
|
3449
3443
|
* Komponenten er rent informativ og aldri fokuserbar (setter aldri tabindex).
|
|
@@ -3457,7 +3451,7 @@ function isIOS() {
|
|
|
3457
3451
|
var progressBarCounter = 0;
|
|
3458
3452
|
var STATE_ICON = {
|
|
3459
3453
|
success: "check",
|
|
3460
|
-
error: "
|
|
3454
|
+
error: "close"
|
|
3461
3455
|
};
|
|
3462
3456
|
var STATE_STATUS = {
|
|
3463
3457
|
success: "success",
|
|
@@ -3535,8 +3529,9 @@ var IxProgressBar = class extends HTMLElement {
|
|
|
3535
3529
|
this.style.setProperty("--ii-progress-bar-fill", `${fillPercent}%`);
|
|
3536
3530
|
this._label.textContent = label ?? "";
|
|
3537
3531
|
this._label.hidden = !label;
|
|
3538
|
-
this.
|
|
3532
|
+
this._updateValue(clamped, showValue, isActive, Boolean(label));
|
|
3539
3533
|
this._supportText.textContent = supportText ?? "";
|
|
3534
|
+
this._updateStatusMessage(state);
|
|
3540
3535
|
Boolean(label) || this.hasAttribute("aria-label") || this.hasAttribute("aria-labelledby");
|
|
3541
3536
|
if (isActive) {
|
|
3542
3537
|
this.removeAttribute("data-status");
|
|
@@ -3572,32 +3567,28 @@ var IxProgressBar = class extends HTMLElement {
|
|
|
3572
3567
|
this.setAttribute("data-status", STATE_STATUS[state]);
|
|
3573
3568
|
}
|
|
3574
3569
|
}
|
|
3570
|
+
/** Prosentverdien vises i header-slutten, bare i active. */
|
|
3571
|
+
_updateValue(clamped, showValue, isActive, hasLabel) {
|
|
3572
|
+
if (!this._headerEnd || !this._header) return;
|
|
3573
|
+
const visible = isActive && showValue;
|
|
3574
|
+
this._headerEnd.textContent = visible ? `${Math.round(clamped)}\u00A0%` : "";
|
|
3575
|
+
this._headerEnd.hidden = !visible;
|
|
3576
|
+
this._header.hidden = !hasLabel && !visible;
|
|
3577
|
+
}
|
|
3575
3578
|
/**
|
|
3576
|
-
*
|
|
3577
|
-
*
|
|
3578
|
-
* - success/error med label → ikon i header-slutten (til høyre for label).
|
|
3579
|
-
* - success/error uten label → ikon foran støtteteksten.
|
|
3579
|
+
* I success/error får støtteteksten badge-ikon foran. Ikonet vises også
|
|
3580
|
+
* uten tekst, så tilstanden ikke bare vises med farge (WCAG 1.4.1).
|
|
3580
3581
|
*/
|
|
3581
|
-
|
|
3582
|
-
if (!this.
|
|
3583
|
-
if (
|
|
3584
|
-
this.
|
|
3585
|
-
|
|
3586
|
-
|
|
3587
|
-
|
|
3588
|
-
|
|
3589
|
-
|
|
3590
|
-
|
|
3591
|
-
if (!isActive) {
|
|
3592
|
-
const icon = this._ensureIcon();
|
|
3593
|
-
icon.setAttribute("name", STATE_ICON[state]);
|
|
3594
|
-
if (hasLabel) {
|
|
3595
|
-
this._headerEnd.hidden = true;
|
|
3596
|
-
this._header.appendChild(icon);
|
|
3597
|
-
} else this._support.insertBefore(icon, this._supportText);
|
|
3598
|
-
} else this._removeIcon();
|
|
3599
|
-
const headerHasContent = hasLabel || isActive && showValue || !isActive && hasLabel;
|
|
3600
|
-
this._header.hidden = !headerHasContent;
|
|
3582
|
+
_updateStatusMessage(state) {
|
|
3583
|
+
if (!this._support || !this._supportText) return;
|
|
3584
|
+
if (state === "active") {
|
|
3585
|
+
this._icon?.remove();
|
|
3586
|
+
return;
|
|
3587
|
+
}
|
|
3588
|
+
const icon = this._ensureIcon();
|
|
3589
|
+
icon.setAttribute("name", STATE_ICON[state]);
|
|
3590
|
+
icon.setAttribute("data-status", STATE_STATUS[state]);
|
|
3591
|
+
this._support.insertBefore(icon, this._supportText);
|
|
3601
3592
|
}
|
|
3602
3593
|
_ensureIcon() {
|
|
3603
3594
|
if (!this._icon) {
|
|
@@ -3607,9 +3598,6 @@ var IxProgressBar = class extends HTMLElement {
|
|
|
3607
3598
|
}
|
|
3608
3599
|
return this._icon;
|
|
3609
3600
|
}
|
|
3610
|
-
_removeIcon() {
|
|
3611
|
-
this._icon?.remove();
|
|
3612
|
-
}
|
|
3613
3601
|
_resolveState() {
|
|
3614
3602
|
const raw = this.getAttribute("data-state");
|
|
3615
3603
|
if (raw === "success" || raw === "error") return raw;
|
|
@@ -3628,64 +3616,32 @@ var IxProgressBar = class extends HTMLElement {
|
|
|
3628
3616
|
else this.removeAttribute(attr);
|
|
3629
3617
|
}
|
|
3630
3618
|
};
|
|
3631
|
-
|
|
3632
|
-
//#region lib/components/radio-group/IxRadioGroup.ts
|
|
3619
|
+
// lib/components/radio-group/IxRadioGroup.ts
|
|
3633
3620
|
/**
|
|
3634
|
-
* IxRadioGroup
|
|
3635
|
-
*
|
|
3636
|
-
* ## Designfilosofi
|
|
3637
|
-
*
|
|
3638
|
-
* Komponenten erstatter ingen native HTML-elementer. Forfatteren skriver
|
|
3639
|
-
* native <input type="radio">, <label> og hjelpetekster inni <ix-radio-group>.
|
|
3640
|
-
* Komponenten sin eneste jobb er å sette opp ARIA-koblingene mellom dem.
|
|
3641
|
-
*
|
|
3642
|
-
* ## Hva komponenten gjør
|
|
3643
|
-
*
|
|
3644
|
-
* 1. Setter role="radiogroup" på host slik at skjermlesere annonserer gruppen
|
|
3645
|
-
* med riktig rolle og leser legend-teksten som gruppens tilgjengelige navn.
|
|
3646
|
-
*
|
|
3647
|
-
* 2. Kobler legend via aria-labelledby — genererer ID på [data-field="legend"]
|
|
3648
|
-
* om nødvendig og peker host til den.
|
|
3649
|
-
*
|
|
3650
|
-
* 3. Setter aria-describedby på host med IDer til description og error
|
|
3651
|
-
* (description alltid først). Hopper over elementer som mangler.
|
|
3652
|
-
*
|
|
3653
|
-
* 4. Genererer stabile IDer på hvert input og setter htmlFor på matchende label
|
|
3654
|
-
* om label ikke allerede har det. Respekterer alltid eksisterende IDer.
|
|
3621
|
+
* IxRadioGroup kobler ARIA mellom native radioknapper, legend og hjelpetekster.
|
|
3622
|
+
* Forfatteren skriver markupen selv.
|
|
3655
3623
|
*
|
|
3656
|
-
*
|
|
3657
|
-
*
|
|
3658
|
-
*
|
|
3659
|
-
*
|
|
3660
|
-
*
|
|
3661
|
-
*
|
|
3662
|
-
*
|
|
3663
|
-
* tomt → aria-invalid fjernes. aria-invalid settes kun på host (ikke per
|
|
3664
|
-
* input) — det er gruppens validitet som telles, og en duplisert flagg
|
|
3665
|
-
* per input gir ingen merverdi for skjermlesere.
|
|
3666
|
-
*
|
|
3667
|
-
* 7. Propagerer disabled/readonly til alle inputs via attributeChangedCallback.
|
|
3668
|
-
* Per-knapp disabled bevares — settes med en WeakMap ved første _wire().
|
|
3669
|
-
*
|
|
3670
|
-
* 8. Lytter på childList på host slik at nye <input type="radio">-elementer
|
|
3671
|
-
* som legges til etter mount automatisk wires (id, name, htmlFor, disabled).
|
|
3672
|
-
*
|
|
3673
|
-
* ## Ingen Shadow DOM
|
|
3674
|
-
*
|
|
3675
|
-
* Komponenten bruker light DOM slik at CSS fra @sb1/indeks-css når alle
|
|
3676
|
-
* delelementer direkte.
|
|
3624
|
+
* - role="radiogroup" og aria-labelledby til [data-field="legend"]
|
|
3625
|
+
* - aria-describedby til [data-field="description"] og [data-field="error"]
|
|
3626
|
+
* - id på hver input og for på labelen i samme forelder, når de mangler
|
|
3627
|
+
* - samme name på alle inputs: name på host, ellers name fra en input, ellers generert
|
|
3628
|
+
* - aria-invalid="true" på host (ikke per input) når error-feltet har tekst
|
|
3629
|
+
* - disabled, readonly og required på host gjelder hele gruppen
|
|
3630
|
+
* - inputs som legges til etter mount, kobles på samme måte
|
|
3677
3631
|
*
|
|
3678
3632
|
* @example
|
|
3679
3633
|
* <ix-radio-group>
|
|
3680
3634
|
* <span data-field="legend">Velg kundetype</span>
|
|
3681
3635
|
* <span data-field="description">Beskrivelse</span>
|
|
3682
|
-
* <div
|
|
3683
|
-
* <
|
|
3684
|
-
*
|
|
3685
|
-
*
|
|
3686
|
-
*
|
|
3687
|
-
* <
|
|
3688
|
-
*
|
|
3636
|
+
* <div data-field="items">
|
|
3637
|
+
* <div>
|
|
3638
|
+
* <input type="radio" value="privat" />
|
|
3639
|
+
* <label>Privat</label>
|
|
3640
|
+
* </div>
|
|
3641
|
+
* <div>
|
|
3642
|
+
* <input type="radio" value="bedrift" />
|
|
3643
|
+
* <label>Bedrift</label>
|
|
3644
|
+
* </div>
|
|
3689
3645
|
* </div>
|
|
3690
3646
|
* <span data-field="error"></span>
|
|
3691
3647
|
* </ix-radio-group>
|
|
@@ -3751,7 +3707,8 @@ var IxRadioGroup = class extends HTMLElement {
|
|
|
3751
3707
|
this._errorObserver.observe(error, {
|
|
3752
3708
|
childList: true,
|
|
3753
3709
|
characterData: true,
|
|
3754
|
-
subtree: true
|
|
3710
|
+
subtree: true,
|
|
3711
|
+
attributeFilter: ["data-status"]
|
|
3755
3712
|
});
|
|
3756
3713
|
this._syncInvalid(error);
|
|
3757
3714
|
}
|
|
@@ -3794,7 +3751,7 @@ var IxRadioGroup = class extends HTMLElement {
|
|
|
3794
3751
|
if (inputs.length === 0) return;
|
|
3795
3752
|
for (const input of inputs) {
|
|
3796
3753
|
if (!input.id) input.id = `ix-radio-${++radioInputCounter}`;
|
|
3797
|
-
const wrapper = input.
|
|
3754
|
+
const wrapper = input.parentElement;
|
|
3798
3755
|
if (wrapper) {
|
|
3799
3756
|
const label = wrapper.querySelector("label");
|
|
3800
3757
|
if (label && !label.htmlFor) label.htmlFor = input.id;
|
|
@@ -3807,7 +3764,7 @@ var IxRadioGroup = class extends HTMLElement {
|
|
|
3807
3764
|
this._syncHostStateToInputs();
|
|
3808
3765
|
}
|
|
3809
3766
|
_syncInvalid(error) {
|
|
3810
|
-
if (!!error.textContent?.trim()) this.setAttribute("aria-invalid", "true");
|
|
3767
|
+
if (!!error.textContent?.trim() && error.dataset.status !== "success") this.setAttribute("aria-invalid", "true");
|
|
3811
3768
|
else this.removeAttribute("aria-invalid");
|
|
3812
3769
|
}
|
|
3813
3770
|
_syncHostStateToInputs() {
|
|
@@ -3833,69 +3790,30 @@ var IxRadioGroup = class extends HTMLElement {
|
|
|
3833
3790
|
}
|
|
3834
3791
|
}
|
|
3835
3792
|
};
|
|
3836
|
-
|
|
3837
|
-
//#region lib/components/tabs/IxTabs.ts
|
|
3793
|
+
// lib/components/tabs/IxTabs.ts
|
|
3838
3794
|
/**
|
|
3839
|
-
* IxTabs — ARIA-lim for faner
|
|
3840
|
-
*
|
|
3841
|
-
*
|
|
3842
|
-
*
|
|
3843
|
-
*
|
|
3844
|
-
* HTML — en liste med faner og ett panel per fane — og <ix-tabs> kobler dem
|
|
3845
|
-
* sammen med ARIA, roving tabindex, tastaturnavigasjon og panel-visning.
|
|
3846
|
-
* Logikken er portet fra u-elements (u-tabs), men til Indeks' light-DOM-
|
|
3847
|
-
* konvensjon (ingen Shadow DOM, ingen ::slotted).
|
|
3848
|
-
*
|
|
3849
|
-
* Fire custom elements samarbeider:
|
|
3850
|
-
* - <ix-tabs> — rot og koordinator. Eier all kabling og tastatur.
|
|
3851
|
-
* - <ix-tab-list> — role="tablist", horisontal orientering.
|
|
3852
|
-
* - <ix-tab> — role="tab", én per fane.
|
|
3853
|
-
* - <ix-tab-panel> — role="tabpanel", ett innholdspanel per fane.
|
|
3854
|
-
*
|
|
3855
|
-
* Underelementene setter kun sin egen `role` (så de fungerer isolert); <ix-tabs>
|
|
3856
|
-
* er den autoritative koordinatoren og kjører ved connectedCallback.
|
|
3857
|
-
*
|
|
3858
|
-
* ## Hva <ix-tabs> gjør
|
|
3859
|
-
*
|
|
3860
|
-
* 1. Setter role="tablist" + aria-orientation="horizontal" på tab-lista,
|
|
3861
|
-
* role="tab" på fanene og role="tabpanel" på panelene.
|
|
3862
|
-
* 2. Genererer stabile IDer på faner og paneler (der de mangler) og setter opp
|
|
3863
|
-
* kryssreferansene aria-controls (fane → panel) og aria-labelledby
|
|
3864
|
-
* (panel → fane). Eksisterende IDer og aria-controls respekteres. Fane[i]
|
|
3865
|
-
* kobles til panel[i], eller til panelet fanens aria-controls peker på.
|
|
3866
|
-
* 3. Manuell aktivering: piltast (Venstre/Høyre) og Home/End flytter kun FOKUS
|
|
3867
|
-
* via roving tabindex (fokusert fane får tabindex=0, resten -1).
|
|
3868
|
-
* Piltast-navigasjonen looper rundt endene. Enter/Space (og klikk) aktiverer
|
|
3869
|
-
* den fokuserte fanen.
|
|
3870
|
-
* 4. Kun én fane er valgt om gangen. Ved aktivering flyttes aria-selected +
|
|
3871
|
-
* tabindex til ny fane, tilhørende panel vises, øvrige skjules ([hidden] +
|
|
3872
|
-
* aria-hidden). Panelet gjøres ikke fokuserbart — det er ikke en egen
|
|
3873
|
-
* tab-stopp; innholdet i panelet bærer sin egen fokusrekkefølge.
|
|
3874
|
-
* Fane→panel-kobling skjer via delt data-value (ellers aria-controls,
|
|
3875
|
-
* ellers posisjon) — se _getPanel.
|
|
3876
|
-
* 5. Minst én fane er valgt ved mount — enten den som har aria-selected="true"
|
|
3877
|
-
* i markup, ellers den første fanen.
|
|
3878
|
-
* 6. Ved aktivering (klikk/tastatur) sendes et bubbling CustomEvent('change')
|
|
3879
|
-
* på <ix-tabs> slik at en React-wrapper kan lese aria-selected og kalle
|
|
3880
|
-
* onChange. Programmatisk init og ekstern (kontrollert) synk sender IKKE
|
|
3881
|
-
* change — kun ekte brukerinteraksjon.
|
|
3882
|
-
* 7. En MutationObserver re-wirer når faner/paneler legges til eller fjernes
|
|
3883
|
-
* (typisk React conditional rendering), og reagerer på ekstern endring av
|
|
3884
|
-
* aria-selected (kontrollert React-modus) ved å reconcile-e resten.
|
|
3885
|
-
*
|
|
3886
|
-
* ## Ingen Shadow DOM
|
|
3795
|
+
* IxTabs — ARIA-lim for faner i light DOM. Forfatteren skriver en liste med
|
|
3796
|
+
* faner og ett panel per fane; <ix-tabs> kobler dem med ARIA, roving tabindex,
|
|
3797
|
+
* tastaturnavigasjon og panel-visning. Portet fra u-elements (u-tabs), men uten
|
|
3798
|
+
* Shadow DOM, slik at CSS treffer custom elementene og ARIA-relasjonene virker
|
|
3799
|
+
* på tvers.
|
|
3887
3800
|
*
|
|
3888
|
-
*
|
|
3889
|
-
*
|
|
3801
|
+
* - Manuell aktivering: piltaster og Home/End flytter bare fokus; Enter/Space
|
|
3802
|
+
* og klikk aktiverer. Panelet er ikke en egen tab-stopp.
|
|
3803
|
+
* - Fane og panel kobles via aria-controls, ellers delt data-value, ellers
|
|
3804
|
+
* posisjon (se _getPanel).
|
|
3805
|
+
* - Ved klikk/tastatur sendes et bubbling `change` på <ix-tabs>. Init og
|
|
3806
|
+
* ekstern (kontrollert) synk sender ikke change.
|
|
3807
|
+
* - Underelementene setter bare sin egen `role`; <ix-tabs> er koordinatoren.
|
|
3890
3808
|
*
|
|
3891
3809
|
* @example
|
|
3892
3810
|
* <ix-tabs class="ix-tabs">
|
|
3893
|
-
* <ix-tab-list
|
|
3894
|
-
* <ix-tab
|
|
3895
|
-
* <ix-tab
|
|
3811
|
+
* <ix-tab-list>
|
|
3812
|
+
* <ix-tab>Oversikt</ix-tab>
|
|
3813
|
+
* <ix-tab>Detaljer</ix-tab>
|
|
3896
3814
|
* </ix-tab-list>
|
|
3897
|
-
* <ix-tab-panel
|
|
3898
|
-
* <ix-tab-panel
|
|
3815
|
+
* <ix-tab-panel>Oversikt-innhold</ix-tab-panel>
|
|
3816
|
+
* <ix-tab-panel>Detaljer-innhold</ix-tab-panel>
|
|
3899
3817
|
* </ix-tabs>
|
|
3900
3818
|
*/
|
|
3901
3819
|
var tabsCounter = 0;
|
|
@@ -4104,8 +4022,7 @@ var IxTabPanel = class extends HTMLElement {
|
|
|
4104
4022
|
if (!this.getAttribute(ROLE)) this.setAttribute(ROLE, "tabpanel");
|
|
4105
4023
|
}
|
|
4106
4024
|
};
|
|
4107
|
-
|
|
4108
|
-
//#region lib/utils/cn.ts
|
|
4025
|
+
// lib/utils/cn.ts
|
|
4109
4026
|
function cn(...inputs) {
|
|
4110
4027
|
const klasser = [];
|
|
4111
4028
|
for (const input of inputs) {
|
|
@@ -4120,8 +4037,7 @@ function cn(...inputs) {
|
|
|
4120
4037
|
}
|
|
4121
4038
|
return klasser.join(" ");
|
|
4122
4039
|
}
|
|
4123
|
-
|
|
4124
|
-
//#region lib/modal/modal.ts
|
|
4040
|
+
// lib/modal/modal.ts
|
|
4125
4041
|
var ATTR_OPEN = "data-modal-open";
|
|
4126
4042
|
var ATTR_CLOSE = "data-modal-close";
|
|
4127
4043
|
var ATTR_NO_BACKDROP = "data-no-close-on-backdrop";
|
|
@@ -4174,22 +4090,24 @@ function handleClose(e) {
|
|
|
4174
4090
|
}
|
|
4175
4091
|
var observer = new MutationObserver((records) => {
|
|
4176
4092
|
for (const record of records) {
|
|
4093
|
+
if (record.type !== "attributes") continue;
|
|
4177
4094
|
const dialog = record.target;
|
|
4178
4095
|
if (!(dialog instanceof HTMLDialogElement)) continue;
|
|
4179
4096
|
if (!dialog.classList.contains("ix-modal")) continue;
|
|
4180
4097
|
if (dialog.open) handleOpened(dialog);
|
|
4181
4098
|
else handleClosed(dialog);
|
|
4182
4099
|
}
|
|
4100
|
+
for (const dialog of openModals) if (!dialog.isConnected) handleClosed(dialog);
|
|
4183
4101
|
});
|
|
4184
4102
|
document.addEventListener("click", handleClick);
|
|
4185
4103
|
document.addEventListener("close", handleClose, true);
|
|
4186
4104
|
observer.observe(document.documentElement, {
|
|
4187
4105
|
subtree: true,
|
|
4106
|
+
childList: true,
|
|
4188
4107
|
attributes: true,
|
|
4189
4108
|
attributeFilter: ["open"]
|
|
4190
4109
|
});
|
|
4191
|
-
|
|
4192
|
-
//#region lib/tooltip/tooltip.ts
|
|
4110
|
+
// lib/tooltip/tooltip.ts
|
|
4193
4111
|
var TOOLTIP_ID = "ix-tooltip-singleton";
|
|
4194
4112
|
var ATTR = "data-tooltip";
|
|
4195
4113
|
var ATTR_PLACEMENT = "data-tooltip-placement";
|
|
@@ -4306,8 +4224,7 @@ document.addEventListener("mouseout", handleMouseout);
|
|
|
4306
4224
|
document.addEventListener("focusin", handleFocusin);
|
|
4307
4225
|
document.addEventListener("focusout", handleFocusout);
|
|
4308
4226
|
document.addEventListener("keydown", handleKeydown);
|
|
4309
|
-
|
|
4310
|
-
//#region index.ts
|
|
4227
|
+
// index.ts
|
|
4311
4228
|
customElements.define("ix-field", IxField);
|
|
4312
4229
|
customElements.define("ix-icon", IxIcon);
|
|
4313
4230
|
customElements.define("ix-radio-group", IxRadioGroup);
|
|
@@ -4323,5 +4240,4 @@ customElements.define("ix-tab-list", IxTabList);
|
|
|
4323
4240
|
customElements.define("ix-tab", IxTab);
|
|
4324
4241
|
customElements.define("ix-tab-panel", IxTabPanel);
|
|
4325
4242
|
globalThis.cn = cn;
|
|
4326
|
-
//#endregion
|
|
4327
4243
|
export { BUILTIN_FORMAT_NAMES, COMMON_ICON_NAMES, IxCheckboxGroup, IxCombobox, IxDateField, IxDropdown, IxField, IxIcon, IxPhoneNumberField, IxPopover, IxProgressBar, IxRadioGroup, IxTab, IxTabList, IxTabPanel, IxTabs, amountFormatterForLocale, cn, createAmountFormatter, createPatternFormatter, registerFormat, resolveFormat };
|
|
@@ -43,7 +43,7 @@
|
|
|
43
43
|
* @example Single
|
|
44
44
|
* <ix-combobox data-no-hits-text="Ingen treff">
|
|
45
45
|
* <div class="ix-text-field">
|
|
46
|
-
* <input
|
|
46
|
+
* <input placeholder="Søk …" />
|
|
47
47
|
* <button class="ix-combobox__toggle"></button>
|
|
48
48
|
* </div>
|
|
49
49
|
* <div class="ix-combobox__listbox">
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* IxProgressBar — viser fremdrift i én sammenhengende prosess.
|
|
3
3
|
*
|
|
4
4
|
* DOM-generator: web componenten eier og genererer all indre HTML (header med
|
|
5
|
-
* label + verdi
|
|
5
|
+
* label + verdi, track + fill, og en støttetekst-region).
|
|
6
6
|
* Forfatteren skriver kun host-elementet `<ix-progress-bar>` med attributter —
|
|
7
7
|
* ingen intern struktur.
|
|
8
8
|
*
|
|
@@ -10,9 +10,11 @@
|
|
|
10
10
|
* - `active` (default): role="progressbar" med aria-valuenow/min/max på host.
|
|
11
11
|
* Nøytral/primær fremdriftsfarge. Ingen statusikon. Kan vise prosent.
|
|
12
12
|
* - `success` / `error`: rollen `progressbar` FJERNES (prosessen er ferdig/avbrutt).
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
13
|
+
* `data-status` (success/danger) kobler --ix-color-status-* til fyllet og
|
|
14
|
+
* til støtteteksten, som får badge-ikon foran og ser ut som en
|
|
15
|
+
* ValidationMessage, slik Figma viser. Den bruker ikke `data-field="error"`:
|
|
16
|
+
* ix-field og gruppene leter etter det i hele subtreet og ville tatt
|
|
17
|
+
* meldingen for feltets feilmelding. Støtteteksten er en aria-live-region
|
|
16
18
|
* (role="status") så overgangen annonseres politely.
|
|
17
19
|
*
|
|
18
20
|
* Komponenten er rent informativ og aldri fokuserbar (setter aldri tabindex).
|
|
@@ -41,15 +43,14 @@ export declare class IxProgressBar extends HTMLElement {
|
|
|
41
43
|
/** Bygger den indre strukturen én gang (idempotent). */
|
|
42
44
|
private _ensureStructure;
|
|
43
45
|
private _update;
|
|
46
|
+
/** Prosentverdien vises i header-slutten, bare i active. */
|
|
47
|
+
private _updateValue;
|
|
44
48
|
/**
|
|
45
|
-
*
|
|
46
|
-
*
|
|
47
|
-
* - success/error med label → ikon i header-slutten (til høyre for label).
|
|
48
|
-
* - success/error uten label → ikon foran støtteteksten.
|
|
49
|
+
* I success/error får støtteteksten badge-ikon foran. Ikonet vises også
|
|
50
|
+
* uten tekst, så tilstanden ikke bare vises med farge (WCAG 1.4.1).
|
|
49
51
|
*/
|
|
50
|
-
private
|
|
52
|
+
private _updateStatusMessage;
|
|
51
53
|
private _ensureIcon;
|
|
52
|
-
private _removeIcon;
|
|
53
54
|
private _resolveState;
|
|
54
55
|
/** Klamper til 0–100. Ikke-numerisk → 0. */
|
|
55
56
|
private _clampValue;
|
|
@@ -1,59 +1,28 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* IxRadioGroup
|
|
2
|
+
* IxRadioGroup kobler ARIA mellom native radioknapper, legend og hjelpetekster.
|
|
3
|
+
* Forfatteren skriver markupen selv.
|
|
3
4
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
* 1. Setter role="radiogroup" på host slik at skjermlesere annonserer gruppen
|
|
13
|
-
* med riktig rolle og leser legend-teksten som gruppens tilgjengelige navn.
|
|
14
|
-
*
|
|
15
|
-
* 2. Kobler legend via aria-labelledby — genererer ID på [data-field="legend"]
|
|
16
|
-
* om nødvendig og peker host til den.
|
|
17
|
-
*
|
|
18
|
-
* 3. Setter aria-describedby på host med IDer til description og error
|
|
19
|
-
* (description alltid først). Hopper over elementer som mangler.
|
|
20
|
-
*
|
|
21
|
-
* 4. Genererer stabile IDer på hvert input og setter htmlFor på matchende label
|
|
22
|
-
* om label ikke allerede har det. Respekterer alltid eksisterende IDer.
|
|
23
|
-
*
|
|
24
|
-
* 5. Synkroniserer name-attributt på alle inputs — name er mekanismen
|
|
25
|
-
* nettleseren bruker for mutual exclusivity (ett valg av gangen) og
|
|
26
|
-
* ArrowKey-navigasjon mellom alternativene. Alle inputs i gruppen må ha
|
|
27
|
-
* samme name. Prioritet: name på host > name på input > auto-generert.
|
|
28
|
-
*
|
|
29
|
-
* 6. Setter aria-live="polite" på [data-field="error"] og observerer
|
|
30
|
-
* textContent-endringer: ikke-tomt innhold → aria-invalid="true" på host;
|
|
31
|
-
* tomt → aria-invalid fjernes. aria-invalid settes kun på host (ikke per
|
|
32
|
-
* input) — det er gruppens validitet som telles, og en duplisert flagg
|
|
33
|
-
* per input gir ingen merverdi for skjermlesere.
|
|
34
|
-
*
|
|
35
|
-
* 7. Propagerer disabled/readonly til alle inputs via attributeChangedCallback.
|
|
36
|
-
* Per-knapp disabled bevares — settes med en WeakMap ved første _wire().
|
|
37
|
-
*
|
|
38
|
-
* 8. Lytter på childList på host slik at nye <input type="radio">-elementer
|
|
39
|
-
* som legges til etter mount automatisk wires (id, name, htmlFor, disabled).
|
|
40
|
-
*
|
|
41
|
-
* ## Ingen Shadow DOM
|
|
42
|
-
*
|
|
43
|
-
* Komponenten bruker light DOM slik at CSS fra @sb1/indeks-css når alle
|
|
44
|
-
* delelementer direkte.
|
|
5
|
+
* - role="radiogroup" og aria-labelledby til [data-field="legend"]
|
|
6
|
+
* - aria-describedby til [data-field="description"] og [data-field="error"]
|
|
7
|
+
* - id på hver input og for på labelen i samme forelder, når de mangler
|
|
8
|
+
* - samme name på alle inputs: name på host, ellers name fra en input, ellers generert
|
|
9
|
+
* - aria-invalid="true" på host (ikke per input) når error-feltet har tekst
|
|
10
|
+
* - disabled, readonly og required på host gjelder hele gruppen
|
|
11
|
+
* - inputs som legges til etter mount, kobles på samme måte
|
|
45
12
|
*
|
|
46
13
|
* @example
|
|
47
14
|
* <ix-radio-group>
|
|
48
15
|
* <span data-field="legend">Velg kundetype</span>
|
|
49
16
|
* <span data-field="description">Beskrivelse</span>
|
|
50
|
-
* <div
|
|
51
|
-
* <
|
|
52
|
-
*
|
|
53
|
-
*
|
|
54
|
-
*
|
|
55
|
-
* <
|
|
56
|
-
*
|
|
17
|
+
* <div data-field="items">
|
|
18
|
+
* <div>
|
|
19
|
+
* <input type="radio" value="privat" />
|
|
20
|
+
* <label>Privat</label>
|
|
21
|
+
* </div>
|
|
22
|
+
* <div>
|
|
23
|
+
* <input type="radio" value="bedrift" />
|
|
24
|
+
* <label>Bedrift</label>
|
|
25
|
+
* </div>
|
|
57
26
|
* </div>
|
|
58
27
|
* <span data-field="error"></span>
|
|
59
28
|
* </ix-radio-group>
|
|
@@ -1,64 +1,26 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* IxTabs — ARIA-lim for faner
|
|
2
|
+
* IxTabs — ARIA-lim for faner i light DOM. Forfatteren skriver en liste med
|
|
3
|
+
* faner og ett panel per fane; <ix-tabs> kobler dem med ARIA, roving tabindex,
|
|
4
|
+
* tastaturnavigasjon og panel-visning. Portet fra u-elements (u-tabs), men uten
|
|
5
|
+
* Shadow DOM, slik at CSS treffer custom elementene og ARIA-relasjonene virker
|
|
6
|
+
* på tvers.
|
|
3
7
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
* Fire custom elements samarbeider:
|
|
13
|
-
* - <ix-tabs> — rot og koordinator. Eier all kabling og tastatur.
|
|
14
|
-
* - <ix-tab-list> — role="tablist", horisontal orientering.
|
|
15
|
-
* - <ix-tab> — role="tab", én per fane.
|
|
16
|
-
* - <ix-tab-panel> — role="tabpanel", ett innholdspanel per fane.
|
|
17
|
-
*
|
|
18
|
-
* Underelementene setter kun sin egen `role` (så de fungerer isolert); <ix-tabs>
|
|
19
|
-
* er den autoritative koordinatoren og kjører ved connectedCallback.
|
|
20
|
-
*
|
|
21
|
-
* ## Hva <ix-tabs> gjør
|
|
22
|
-
*
|
|
23
|
-
* 1. Setter role="tablist" + aria-orientation="horizontal" på tab-lista,
|
|
24
|
-
* role="tab" på fanene og role="tabpanel" på panelene.
|
|
25
|
-
* 2. Genererer stabile IDer på faner og paneler (der de mangler) og setter opp
|
|
26
|
-
* kryssreferansene aria-controls (fane → panel) og aria-labelledby
|
|
27
|
-
* (panel → fane). Eksisterende IDer og aria-controls respekteres. Fane[i]
|
|
28
|
-
* kobles til panel[i], eller til panelet fanens aria-controls peker på.
|
|
29
|
-
* 3. Manuell aktivering: piltast (Venstre/Høyre) og Home/End flytter kun FOKUS
|
|
30
|
-
* via roving tabindex (fokusert fane får tabindex=0, resten -1).
|
|
31
|
-
* Piltast-navigasjonen looper rundt endene. Enter/Space (og klikk) aktiverer
|
|
32
|
-
* den fokuserte fanen.
|
|
33
|
-
* 4. Kun én fane er valgt om gangen. Ved aktivering flyttes aria-selected +
|
|
34
|
-
* tabindex til ny fane, tilhørende panel vises, øvrige skjules ([hidden] +
|
|
35
|
-
* aria-hidden). Panelet gjøres ikke fokuserbart — det er ikke en egen
|
|
36
|
-
* tab-stopp; innholdet i panelet bærer sin egen fokusrekkefølge.
|
|
37
|
-
* Fane→panel-kobling skjer via delt data-value (ellers aria-controls,
|
|
38
|
-
* ellers posisjon) — se _getPanel.
|
|
39
|
-
* 5. Minst én fane er valgt ved mount — enten den som har aria-selected="true"
|
|
40
|
-
* i markup, ellers den første fanen.
|
|
41
|
-
* 6. Ved aktivering (klikk/tastatur) sendes et bubbling CustomEvent('change')
|
|
42
|
-
* på <ix-tabs> slik at en React-wrapper kan lese aria-selected og kalle
|
|
43
|
-
* onChange. Programmatisk init og ekstern (kontrollert) synk sender IKKE
|
|
44
|
-
* change — kun ekte brukerinteraksjon.
|
|
45
|
-
* 7. En MutationObserver re-wirer når faner/paneler legges til eller fjernes
|
|
46
|
-
* (typisk React conditional rendering), og reagerer på ekstern endring av
|
|
47
|
-
* aria-selected (kontrollert React-modus) ved å reconcile-e resten.
|
|
48
|
-
*
|
|
49
|
-
* ## Ingen Shadow DOM
|
|
50
|
-
*
|
|
51
|
-
* Light DOM slik at CSS fra @sb1/indeks-css treffer .ix-tabs__* og tag-
|
|
52
|
-
* selektorene direkte, og ARIA-relasjonene fungerer på tvers av elementene.
|
|
8
|
+
* - Manuell aktivering: piltaster og Home/End flytter bare fokus; Enter/Space
|
|
9
|
+
* og klikk aktiverer. Panelet er ikke en egen tab-stopp.
|
|
10
|
+
* - Fane og panel kobles via aria-controls, ellers delt data-value, ellers
|
|
11
|
+
* posisjon (se _getPanel).
|
|
12
|
+
* - Ved klikk/tastatur sendes et bubbling `change` på <ix-tabs>. Init og
|
|
13
|
+
* ekstern (kontrollert) synk sender ikke change.
|
|
14
|
+
* - Underelementene setter bare sin egen `role`; <ix-tabs> er koordinatoren.
|
|
53
15
|
*
|
|
54
16
|
* @example
|
|
55
17
|
* <ix-tabs class="ix-tabs">
|
|
56
|
-
* <ix-tab-list
|
|
57
|
-
* <ix-tab
|
|
58
|
-
* <ix-tab
|
|
18
|
+
* <ix-tab-list>
|
|
19
|
+
* <ix-tab>Oversikt</ix-tab>
|
|
20
|
+
* <ix-tab>Detaljer</ix-tab>
|
|
59
21
|
* </ix-tab-list>
|
|
60
|
-
* <ix-tab-panel
|
|
61
|
-
* <ix-tab-panel
|
|
22
|
+
* <ix-tab-panel>Oversikt-innhold</ix-tab-panel>
|
|
23
|
+
* <ix-tab-panel>Detaljer-innhold</ix-tab-panel>
|
|
62
24
|
* </ix-tabs>
|
|
63
25
|
*/
|
|
64
26
|
export declare class IxTabs extends HTMLElement {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sb1/indeks-web",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.26.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Web components for Indeks design system",
|
|
6
6
|
"homepage": "https://design.sparebank1.no",
|
|
@@ -40,12 +40,12 @@
|
|
|
40
40
|
"author": "",
|
|
41
41
|
"license": "MIT",
|
|
42
42
|
"devDependencies": {
|
|
43
|
-
"@types/node": "^26.6.
|
|
44
|
-
"jsdom": "^30.1.
|
|
43
|
+
"@types/node": "^26.6.5",
|
|
44
|
+
"jsdom": "^30.1.3",
|
|
45
45
|
"typescript": "~6.0.3",
|
|
46
|
-
"vite": "^8.
|
|
47
|
-
"vite-plugin-dts": "^5.1.
|
|
48
|
-
"vitest": "^
|
|
46
|
+
"vite": "^8.3.5",
|
|
47
|
+
"vite-plugin-dts": "^5.1.2",
|
|
48
|
+
"vitest": "^5.0.5"
|
|
49
49
|
},
|
|
50
50
|
"scripts": {
|
|
51
51
|
"build": "rm -rf ./dist && pnpm run build:npm",
|