@sb1/indeks-web 0.15.0 → 0.17.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.
@@ -0,0 +1,83 @@
1
+ /**
2
+ * IxCheckboxGroup — ARIA-lim for checkbox-grupper.
3
+ *
4
+ * ## Designfilosofi
5
+ *
6
+ * Komponenten erstatter ingen native HTML-elementer. Forfatteren skriver
7
+ * native <input type="checkbox">, <label> og hjelpetekster inni
8
+ * <ix-checkbox-group>. Komponenten sin eneste jobb er å sette opp
9
+ * ARIA-koblingene mellom dem. Speiler <ix-radio-group>, med tre bevisste
10
+ * forskjeller (se under).
11
+ *
12
+ * ## Hva komponenten gjør
13
+ *
14
+ * 1. Setter role="group" på host slik at skjermlesere annonserer gruppen og
15
+ * leser legend-teksten som gruppens tilgjengelige navn. (Det finnes ingen
16
+ * "checkboxgroup"-rolle i ARIA — "group" er korrekt for en checkbox-gruppe.)
17
+ *
18
+ * 2. Kobler legend via aria-labelledby — genererer ID på [data-field="legend"]
19
+ * om nødvendig og peker host til den.
20
+ *
21
+ * 3. Setter aria-describedby på host med IDer til description og error
22
+ * (description alltid først). Hopper over elementer som mangler.
23
+ *
24
+ * 4. Genererer stabile IDer på hvert input og setter htmlFor på matchende label
25
+ * om label ikke allerede har det. Respekterer alltid eksisterende IDer.
26
+ *
27
+ * 5. Setter aria-live="polite" på [data-field="error"] og observerer
28
+ * textContent-endringer: ikke-tomt innhold → aria-invalid="true" på host;
29
+ * tomt → aria-invalid fjernes. aria-invalid settes kun på host.
30
+ *
31
+ * 6. Propagerer disabled/readonly til alle inputs via attributeChangedCallback.
32
+ * Per-knapp disabled bevares — settes med en WeakMap ved første _wire().
33
+ *
34
+ * 7. Lytter på childList på host slik at nye <input type="checkbox">-elementer
35
+ * som legges til etter mount automatisk wires (id, name, htmlFor, disabled).
36
+ *
37
+ * ## Forskjeller fra <ix-radio-group>
38
+ *
39
+ * - **role="group"**, ikke "radiogroup".
40
+ * - **Ingen name-mutex.** Checkbox deler ikke `name` for gjensidig utelukkelse.
41
+ * Host-`name` propageres til alle inputs (for form-innsending under felles
42
+ * navn), men når host ikke har name lar vi forfatterens egne name/value stå
43
+ * — vi auto-genererer ikke et felles name.
44
+ * - **Ingen required-triks.** For radio settes `required` kun på første input
45
+ * (Safari-boble-fiks for mutex-gruppen). For checkbox betyr `required` "denne
46
+ * må krysses", så vi rører ikke required automatisk på gruppenivå.
47
+ * - **Tastatur:** checkbox har ingen pil-navigasjon; readonly blokkerer kun
48
+ * Space (toggle) — ikke piltaster.
49
+ *
50
+ * ## Ingen Shadow DOM
51
+ *
52
+ * Komponenten bruker light DOM slik at CSS fra @sb1/indeks-css når alle
53
+ * delelementer direkte.
54
+ *
55
+ * @example
56
+ * <ix-checkbox-group>
57
+ * <span data-field="legend">Hvordan vil du bli kontaktet?</span>
58
+ * <span data-field="description">Velg én eller flere</span>
59
+ * <div data-field="items">
60
+ * <div class="ix-checkbox">
61
+ * <input type="checkbox" value="epost" />
62
+ * <label>E-post</label>
63
+ * </div>
64
+ * </div>
65
+ * <span data-field="error"></span>
66
+ * </ix-checkbox-group>
67
+ */
68
+ export declare class IxCheckboxGroup extends HTMLElement {
69
+ private _errorObserver;
70
+ private _childObserver;
71
+ private _keydownListener;
72
+ private _instanceId;
73
+ private _ownDisabled;
74
+ static get observedAttributes(): string[];
75
+ connectedCallback(): void;
76
+ disconnectedCallback(): void;
77
+ attributeChangedCallback(attr: string, _oldValue: string | null, _newValue: string | null): void;
78
+ private _wire;
79
+ private _containsCheckboxInput;
80
+ private _wireInputs;
81
+ private _syncInvalid;
82
+ private _syncHostStateToInputs;
83
+ }
@@ -0,0 +1,132 @@
1
+ /**
2
+ * IxCombobox — ARIA-lim for en søkbar select med single- og multiselect.
3
+ *
4
+ * ## Designfilosofi
5
+ *
6
+ * Komponenten erstatter ingen native elementer. Forfatteren skriver <input>,
7
+ * en options-liste og en skjult <select> i light DOM inni <ix-combobox>.
8
+ * Komponentens jobb er å koble dem sammen med korrekt combobox/listbox-ARIA,
9
+ * håndtere tastatur og filtrering, posisjonere lista, og synkronisere valgte
10
+ * verdier til den skjulte <select> for form-innsending.
11
+ *
12
+ * ## Virtual focus
13
+ *
14
+ * DOM-fokus blir ALLTID på <input>. Aktivt listeelement spores med
15
+ * `aria-activedescendant` som peker på option-ens id — fokus flyttes aldri
16
+ * fysisk inn i lista. Dette lar brukeren skrive og pil-navigere samtidig, og
17
+ * er den enkeltbeslutningen hjemmelagde comboboxer oftest bommer på.
18
+ *
19
+ * ## Single vs. multi
20
+ *
21
+ * `multiple` (attributt på host) bytter oppførsel — ikke to komponenter:
22
+ * - Single: valg setter input.value = label, lukker lista, synker én <option>.
23
+ * - Multi: valg legges i et sett, lista holdes åpen, input tømmes, og en
24
+ * <data class="ix-chip" data-removable> rendres i chips-wrapperen.
25
+ * Chips nås med piltast (radiogruppe-modell), ikke egne tab-stopp.
26
+ *
27
+ * ## Posisjonering
28
+ *
29
+ * Lista posisjoneres manuelt med getBoundingClientRect + viewport-clamp (samme
30
+ * grep som tooltip.ts) — position: fixed, flip over feltet ved plassmangel.
31
+ * INGEN popover-API og INGEN avhengigheter: indeks-web er dependency-fritt og
32
+ * bundles til CDN, og manuell posisjonering fungerer på hele browserslist.
33
+ *
34
+ * ## Delelementer (data-field / klasse i light DOM)
35
+ * - .ix-text-field > input → combobox-input
36
+ * - .ix-combobox__toggle → chevron-knapp (åpne/lukke)
37
+ * - .ix-combobox__listbox → listbox-panel
38
+ * - .ix-combobox__option[data-value] → alternativer
39
+ * - .ix-combobox__no-hits → melding ved 0 treff (role="status")
40
+ * - [data-field="chips"] → chips-wrapper (kun multi)
41
+ * - [data-field="native"] (select) → skjult select for form-synk
42
+ *
43
+ * @example Single
44
+ * <ix-combobox data-no-hits-text="Ingen treff">
45
+ * <div class="ix-text-field">
46
+ * <input class="ix-text-field__input" placeholder="Søk …" />
47
+ * <button class="ix-combobox__toggle"></button>
48
+ * </div>
49
+ * <div class="ix-combobox__listbox">
50
+ * <div class="ix-combobox__option" data-value="47">
51
+ * <span class="ix-combobox__option-check" aria-hidden="true"></span>
52
+ * <span class="ix-combobox__option-label">Norge</span>
53
+ * </div>
54
+ * </div>
55
+ * <div class="ix-combobox__no-hits" role="status" hidden>Ingen treff</div>
56
+ * <select data-field="native" hidden></select>
57
+ * </ix-combobox>
58
+ */
59
+ export declare class IxCombobox extends HTMLElement {
60
+ private _instanceId;
61
+ private _input;
62
+ private _listbox;
63
+ private _toggle;
64
+ private _chips;
65
+ private _noHits;
66
+ private _noHitsText;
67
+ private _select;
68
+ private _arrowHint;
69
+ private _results;
70
+ private _lastAnnouncedCount;
71
+ private _announceTimer;
72
+ private _activeOption;
73
+ private _onInput;
74
+ private _onKeydown;
75
+ private _onFocusin;
76
+ private _onClick;
77
+ private _onDocPointer;
78
+ private _onReposition;
79
+ private _optionObserver;
80
+ static get observedAttributes(): string[];
81
+ connectedCallback(): void;
82
+ disconnectedCallback(): void;
83
+ attributeChangedCallback(name: string, _oldValue: string | null, newValue: string | null): void;
84
+ get multiple(): boolean;
85
+ set multiple(value: boolean);
86
+ private get _options();
87
+ private get _visibleOptions();
88
+ private get _isDisabled();
89
+ private _wire;
90
+ private _rewireOptions;
91
+ private _normalizeSelectedAria;
92
+ private get _isOpen();
93
+ private _open;
94
+ private _close;
95
+ private _toggleOpen;
96
+ private _position;
97
+ private _addRepositionListeners;
98
+ private _removeRepositionListeners;
99
+ private _handleInput;
100
+ private _filter;
101
+ private _searchTexts;
102
+ private _match;
103
+ private _updateNoHits;
104
+ private _optionLabelText;
105
+ private _optionDescriptionText;
106
+ private _setActive;
107
+ private _moveActive;
108
+ private _handleKeydown;
109
+ private _handleInputKeydown;
110
+ private _handleChipKeydown;
111
+ private _handleClick;
112
+ private _selectOption;
113
+ private _deselectValue;
114
+ private _syncFromInitialSelection;
115
+ private _chipElements;
116
+ private _addChip;
117
+ private _removeChip;
118
+ private _chipLabel;
119
+ private _syncChipLabels;
120
+ private _syncSelect;
121
+ private _emitChange;
122
+ private _syncControlState;
123
+ private _syncMultiState;
124
+ private _syncChipsGroup;
125
+ private _syncArrowHint;
126
+ private _ensureArrowHintEl;
127
+ private _addDescribedBy;
128
+ private _removeDescribedBy;
129
+ private _ensureResultsEl;
130
+ private _announceResults;
131
+ private _resetResults;
132
+ }
@@ -1,86 +1,42 @@
1
- /**
2
- * IxField — tilgjengelighetslim mellom label, input og hjelpetekster.
3
- *
4
- * ## Designfilosofi
5
- *
6
- * Komponenten erstatter ingen native HTML-elementer. `<label>`, `<input>`,
7
- * `<select>`, `<textarea>` og hjelpetekster skrives som vanlig HTML inne i
8
- * `<ix-field>`. Komponenten sin eneste jobb er å sette opp ARIA-koblingene
9
- * mellom dem — arbeid som er kjedelig å gjøre manuelt og lett å gjøre feil.
10
- *
11
- * Denne tilnærmingen betyr at:
12
- * - Native nettleser-atferd, validering og styling forblir intakt.
13
- * - Alle form-attributter (`required`, `disabled`, `name`, `value`, osv.)
14
- * settes direkte på native elementet — ikke som props til en wrapper.
15
- * - Komponenten kan brukes med alle typer input uten kodeendringer.
16
- *
17
- * ## Hva komponenten gjør
18
- *
19
- * 1. Kobler `<label>` til input via `for`/`id`-paret.
20
- * Genererer en stabil ID på input om den mangler en.
21
- *
22
- * 2. Setter `aria-describedby` på input med IDer til description og
23
- * error-elementet, slik at skjermlesere leser dem opp etter label og verdi.
24
- *
25
- * 3. Setter `aria-live="polite"` på error-elementet.
26
- * Error-elementet skal alltid ligge i DOM — tomt betyr ingen feil.
27
- * Dette er et krav fra ARIA Live Regions: elementet må eksistere i DOM
28
- * *før* innholdet settes inn for at skjermlesere skal fange opp endringen.
29
- * Hadde vi injisert elementet dynamisk ville mange skjermlesere gått glipp
30
- * av meldingen.
31
- *
32
- * Vi bruker *ikke* `role="alert"` fordi det impliserer `aria-live="assertive"`
33
- * som avbryter brukeren. Feilmeldinger i skjema er viktige, men ikke så
34
- * kritiske at de bør avbryte — `polite` venter til skjermleseren er ferdig.
35
- *
36
- * 4. Bruker MutationObserver på error-elementet. Når innholdet endres:
37
- * - Ikke-tomt → `aria-invalid="true"` på input (visuell og semantisk feil)
38
- * - Tomt → `aria-invalid` fjernes
39
- * Applikasjonen styrer altså feilmelding ved å sette/tømme textContent —
40
- * komponenten holder input-tilstanden synkronisert automatisk.
41
- *
42
- * ## Identifisering av delelementer
43
- *
44
- * Komponenten finner delelementer via:
45
- * - `<label>` → første label-element
46
- * - `input | select | textarea` → første native form-kontroll
47
- * - `[data-field="description"]` → hjelpetekst
48
- * - `[data-field="error"]` → feilmelding (alltid til stede, kan være tom)
49
- * - `[data-field="prefix"]` → visuell prefix (skjules med aria-hidden)
50
- * - `[data-field="suffix"]` → visuell suffix (skjules med aria-hidden)
51
- *
52
- * `data-field` brukes fremfor `slot`-attributtet selv om Shadow DOM ikke er i
53
- * bruk. `slot` er et reservert nøkkelord knytt til Shadow DOM og ville vært
54
- * misvisende, samt kunne krasje om `<ix-field>` senere pakkes inn i en
55
- * komponent med Shadow DOM.
56
- *
57
- * ## Ingen Shadow DOM
58
- *
59
- * Komponenten bruker light DOM. Det betyr at CSS fra `@sb1/indeks-css` når
60
- * alle delelementer uten videre, og at ARIA-attributtene er fullt synlige i
61
- * den tilgjengelighetstreet nettleseren eksponerer til hjelpemiddelteknologi.
62
- *
63
- * @example Grunnleggende bruk
64
- * <ix-field>
65
- * <label>E-postadresse</label>
66
- * <span data-field="description">Vi sender kvittering til denne adressen</span>
67
- * <input type="email" name="email" />
68
- * <span data-field="error"></span>
69
- * </ix-field>
70
- *
71
- * @example Sett feilmelding programmatisk
72
- * const errorEl = document.querySelector('ix-field [data-field="error"]');
73
- * errorEl.textContent = 'Oppgi en gyldig e-postadresse';
74
- * // → input får automatisk aria-invalid="true"
75
- *
76
- * errorEl.textContent = '';
77
- * // → aria-invalid fjernes
78
- */
1
+ import { registerFormat, FieldFormatter } from './formats.js';
79
2
  export declare class IxField extends HTMLElement {
80
3
  private _observer;
81
4
  private _stateObserver;
82
5
  private _charCountListener;
83
6
  private _charCountControl;
7
+ private _formatterProp;
8
+ private _formatTeardown;
9
+ private _activeFormatter;
10
+ private _activeControl;
11
+ private _formatMirror;
12
+ /**
13
+ * Registrer en navngitt formatter globalt. Brukes deretter via
14
+ * `data-format="<navn>"` på et hvilket som helst input inne i et ix-field.
15
+ *
16
+ * @example
17
+ * IxField.registerFormatter('orgnr', { format, parse });
18
+ * // <ix-field><input data-format="orgnr" …></ix-field>
19
+ */
20
+ static registerFormatter: typeof registerFormat;
21
+ /**
22
+ * Sett en formatter direkte på instansen (høyest presedens). Lar konsumenten
23
+ * gi vilkårlig format/parse-logikk som ikke kan uttrykkes som attributt.
24
+ */
25
+ set formatter(value: FieldFormatter | null);
26
+ get formatter(): FieldFormatter | null;
27
+ /**
28
+ * Den rå (uformaterte) verdien til feltet. Sannheten ligger i den skjulte
29
+ * mirror-inputen; uten mirror (feltet mangler `name`) utledes den ved å
30
+ * `parse()` den synlige verdien. Tom streng når ingen formatter er aktiv
31
+ * eller det ikke finnes noe input.
32
+ *
33
+ * Praktisk for ren JS/HTML: `document.querySelector('ix-field').rawValue`.
34
+ * Merk at `<form>`-innsending og `new FormData(form)` også gir rå verdi
35
+ * automatisk, siden mirror-inputen bærer det opprinnelige `name`-et.
36
+ */
37
+ get rawValue(): string;
38
+ /** True når den aktive formatteren formaterer live (mens man skriver). */
39
+ get live(): boolean;
84
40
  static get observedAttributes(): string[];
85
41
  connectedCallback(): void;
86
42
  disconnectedCallback(): void;
@@ -89,6 +45,22 @@ export declare class IxField extends HTMLElement {
89
45
  private _setupCharCount;
90
46
  private _teardownCharCount;
91
47
  private _updateCharCount;
48
+ private _resolveFormatter;
49
+ private _applyLiveOverride;
50
+ private _wireFormatting;
51
+ private _maskInPlace;
52
+ /**
53
+ * Re-anvender formateringen fra en rå verdi.
54
+ *
55
+ * Nyttig for controlled React: når React skriver den rå prop-verdien ved en
56
+ * re-render fyres ikke `input`-eventet, så visningen må oppdateres eksplisitt.
57
+ * Kall denne etter commit (f.eks. i `useLayoutEffect`). No-op når ingen
58
+ * formatter er aktiv.
59
+ *
60
+ * @param raw Ny rå verdi. Utelates den, reformateres gjeldende rå verdi.
61
+ */
62
+ refreshFormat(raw?: string): void;
63
+ private _teardownFormatting;
92
64
  private _syncInvalid;
93
65
  private _syncControlState;
94
66
  private _syncTooltip;
@@ -0,0 +1,101 @@
1
+ /**
2
+ * Input-formatering for ix-field — rene funksjoner, pattern-kompilator og registry.
3
+ *
4
+ * ## Filosofi: vi formaterer, vi masker ikke
5
+ *
6
+ * Dette er IKKE en tegn-maske som avviser tastetrykk. Uansett modus vises ALT
7
+ * brukeren skriver — også ekstra tegn og tegn som ikke «hører hjemme» (en bokstav
8
+ * i et sifferfelt). Feil fanges av validering, ikke ved å droppe tegn. Derfor er
9
+ * `parse` tapsfri (fjerner kun separatorene `format` setter inn).
10
+ *
11
+ * To moduser, styrt av `live`-flagget på formatteren:
12
+ * - **Blur (standard):** feltet formateres når det mister fokus og viser rå
13
+ * (redigerbar) verdi ved fokus. Ingen caret-hopp mens man skriver — en bevisst
14
+ * a11y-beslutning (jf. GOV.UK-frarådingen mot å reformatere telefonnummer mens
15
+ * brukeren skriver).
16
+ * - **Live (opt-in, `live: true`):** separatorene bygger seg opp mens man
17
+ * skriver. De innebygde variantene bruker dette.
18
+ *
19
+ * ## Kontrakten
20
+ *
21
+ * En formatter er et par rene funksjoner:
22
+ * - `format(raw)` : rå verdi → visningsstreng ("12345678" → "123 45 678")
23
+ * - `parse(vist)` : visningsstreng → rå verdi ("123 45 678" → "12345678")
24
+ *
25
+ * Den **rå verdien** er alltid sannheten. Konsumenten lagrer og validerer den rå
26
+ * verdien; visningsstrengen er kun presentasjon. `parse(format(raw)) === raw` skal
27
+ * holde for enhver gyldig rå verdi.
28
+ *
29
+ * ## Tre måter å skaffe en formatter (i ix-field, se IxField.ts)
30
+ *
31
+ * 1. Egen funksjon som property: `el.formatter = { format, parse }`
32
+ * 2. Navngitt via registry: `<input data-format="phone">` (+ `registerFormat(...)`)
33
+ * 3. Pattern-streng, ingen JS: `<input data-format-pattern="000 00 000">`
34
+ *
35
+ * Alle tre gir samme `FieldFormatter`-kontrakt.
36
+ */
37
+ export interface FieldFormatter {
38
+ /** Rå verdi → visningsstreng. */
39
+ format(raw: string): string;
40
+ /**
41
+ * Visningsstreng → rå verdi (sannheten konsumenten lagrer).
42
+ *
43
+ * **Tapsfri:** fjerner kun separatorene formatteren selv setter inn og
44
+ * beholder alt annet — også tegn som ikke «hører hjemme» (en bokstav i et
45
+ * sifferfelt). Vi formaterer, vi masker ikke: feil fanges av validering, ikke
46
+ * ved å droppe tegn. Derfor holder `parse(format(raw)) === raw` for enhver rå
47
+ * verdi, ikke bare gyldige.
48
+ */
49
+ parse(display: string): string;
50
+ /**
51
+ * Formater mens brukeren skriver (separatorer dukker opp fortløpende).
52
+ * Standard `false`/utelatt = format-on-blur (formateres når feltet mister
53
+ * fokus, rå verdi vises ved fokus for fri redigering). De innebygde variantene
54
+ * opter inn på live; egne pattern-strenger og `{format,parse}`-objekter er
55
+ * blur med mindre de setter dette.
56
+ */
57
+ live?: boolean;
58
+ }
59
+ /**
60
+ * Lager en formatter fra en pattern-streng.
61
+ *
62
+ * `format` fyller slots med matchende tegn fra den rå verdien og setter inn
63
+ * separatorer først når neste slot faktisk fylles (ingen dinglende separator på
64
+ * slutten mens man skriver). Tegn som ikke passer neste slot — enten fordi
65
+ * brukeren skrev flere tegn enn patternet har plasser, eller skrev noe som ikke
66
+ * hører hjemme (bokstav i et sifferfelt) — droppes IKKE: den ledende delen som
67
+ * passer formateres, og resten legges uformatert på til slutt i samme
68
+ * rekkefølge brukeren skrev. Alt brukeren skriver forblir synlig.
69
+ *
70
+ * `parse` er tapsfri: den fjerner kun separator-tegnene patternet definerer
71
+ * (literal-tokens) og beholder alt annet i rekkefølge — ingen cap på antall
72
+ * slots, ingen filtrering av «ugyldige» tegn. Slik overlever et feilaktig tegn
73
+ * (bokstav i et sifferfelt) i den rå verdien og kan fanges av validering.
74
+ */
75
+ export declare function createPatternFormatter(pattern: string): FieldFormatter;
76
+ /**
77
+ * Beløp er ikke en fast-lengde pattern-maske: lengden varierer og tusenskillet
78
+ * forskyver seg mens man skriver. Derfor en egen formatter.
79
+ *
80
+ * Rå verdi = siffer med valgfritt desimalskilletegn (`decimalSeparator`), uten
81
+ * tusenskille: `"1234567,89"`. Visning = med tusenskille: `"1 234 567,89"`.
82
+ */
83
+ export declare function createAmountFormatter(options?: {
84
+ groupSeparator?: string;
85
+ decimalSeparator?: string;
86
+ }): FieldFormatter;
87
+ /**
88
+ * Utleder tusenskille og desimalskilletegn for en locale via `Intl.NumberFormat`.
89
+ * Brukes for locale-bevisst beløpsformatering uten å hardkode separatorer.
90
+ */
91
+ export declare function amountFormatterForLocale(locale: string): FieldFormatter;
92
+ /**
93
+ * Registrerer en navngitt formatter som deretter kan brukes via
94
+ * `data-format="<navn>"` på et input inne i et ix-field, hvor som helst i appen.
95
+ *
96
+ * Team kan registrere sine egne varianter (orgnr, kortnummer, …) uten at
97
+ * designsystemet må endres — «felles, men ikke avhengig av oss».
98
+ */
99
+ export declare function registerFormat(name: string, formatter: FieldFormatter): void;
100
+ /** Slår opp en registrert formatter. Returnerer `undefined` om navnet er ukjent. */
101
+ export declare function resolveFormat(name: string): FieldFormatter | undefined;
@@ -0,0 +1,56 @@
1
+ /**
2
+ * IxProgressBar — viser fremdrift i én sammenhengende prosess.
3
+ *
4
+ * DOM-generator: web componenten eier og genererer all indre HTML (header med
5
+ * label + verdi/statusikon, track + fill, og en støttetekst-region).
6
+ * Forfatteren skriver kun host-elementet `<ix-progress-bar>` med attributter —
7
+ * ingen intern struktur.
8
+ *
9
+ * Tilstander (`data-state`):
10
+ * - `active` (default): role="progressbar" med aria-valuenow/min/max på host.
11
+ * Nøytral/primær fremdriftsfarge. Ingen statusikon. Kan vise prosent.
12
+ * - `success` / `error`: rollen `progressbar` FJERNES (prosessen er ferdig/avbrutt).
13
+ * Status formidles semantisk «tilsvarende Message» — `data-status`
14
+ * (success/danger) kobler --ix-color-status-* og et dekorativt
15
+ * `<ix-icon data-badge>` vises alltid. Støtteteksten er en aria-live-region
16
+ * (role="status") så overgangen annonseres politely.
17
+ *
18
+ * Komponenten er rent informativ og aldri fokuserbar (setter aldri tabindex).
19
+ * Ugyldige verdier klampes: value < 0 → 0, > 100 → 100, ikke-numerisk → 0.
20
+ *
21
+ * iOS-workaround: VoiceOver leser ikke løpende `aria-valuenow`-endringer på
22
+ * `role="progressbar"`. På iOS byttes derfor rollen i `active` til `role="img"`
23
+ * og verdien bakes inn i `aria-label` (samme mønster som u-elements sin
24
+ * u-progress). Desktop beholder ekte progressbar-semantikk.
25
+ */
26
+ export declare class IxProgressBar extends HTMLElement {
27
+ private _instanceId;
28
+ private _header;
29
+ private _label;
30
+ private _headerEnd;
31
+ private _track;
32
+ private _fill;
33
+ private _support;
34
+ private _supportText;
35
+ private _icon;
36
+ static get observedAttributes(): string[];
37
+ connectedCallback(): void;
38
+ attributeChangedCallback(_name: string, oldValue: string | null, newValue: string | null): void;
39
+ disconnectedCallback(): void;
40
+ /** Bygger den indre strukturen én gang (idempotent). */
41
+ private _ensureStructure;
42
+ private _update;
43
+ /**
44
+ * Plasserer statusikon og verdi. Følger Figma:
45
+ * - active + data-show-value → «NN %» i header-slutten.
46
+ * - success/error med label → ikon i header-slutten (til høyre for label).
47
+ * - success/error uten label → ikon foran støtteteksten.
48
+ */
49
+ private _updateIconAndValue;
50
+ private _ensureIcon;
51
+ private _removeIcon;
52
+ private _resolveState;
53
+ /** Klamper til 0–100. Ikke-numerisk → 0. */
54
+ private _clampValue;
55
+ private _setAriaRef;
56
+ }
@@ -0,0 +1,121 @@
1
+ /**
2
+ * IxTabs — ARIA-lim for faner (tabs) i light DOM.
3
+ *
4
+ * ## Designfilosofi
5
+ *
6
+ * Komponenten erstatter ingen native elementer. Forfatteren skriver semantisk
7
+ * HTML — en liste med faner og ett panel per fane — og <ix-tabs> kobler dem
8
+ * sammen med ARIA, roving tabindex, tastaturnavigasjon og panel-visning.
9
+ * Logikken er portet fra u-elements (u-tabs), men til Indeks' light-DOM-
10
+ * konvensjon (ingen Shadow DOM, ingen ::slotted).
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.
53
+ *
54
+ * @example
55
+ * <ix-tabs class="ix-tabs">
56
+ * <ix-tab-list class="ix-tabs__list">
57
+ * <ix-tab class="ix-tabs__tab">Oversikt</ix-tab>
58
+ * <ix-tab class="ix-tabs__tab">Detaljer</ix-tab>
59
+ * </ix-tab-list>
60
+ * <ix-tab-panel class="ix-tabs__panel">Oversikt-innhold</ix-tab-panel>
61
+ * <ix-tab-panel class="ix-tabs__panel">Detaljer-innhold</ix-tab-panel>
62
+ * </ix-tabs>
63
+ */
64
+ export declare class IxTabs extends HTMLElement {
65
+ private _mutationObserver;
66
+ private _clickListener;
67
+ private _keydownListener;
68
+ private _instanceId;
69
+ private _internalUpdate;
70
+ connectedCallback(): void;
71
+ disconnectedCallback(): void;
72
+ /** Faner i rekkefølge (direkte barn av tab-lista). */
73
+ private _getTabs;
74
+ /** Paneler i rekkefølge (direkte barn av <ix-tabs>). */
75
+ private _getPanels;
76
+ /**
77
+ * Finn panelet en fane styrer. Prioritet:
78
+ * 1. Eksplisitt `aria-controls` (forfatter- eller WC-satt).
79
+ * 2. Delt `data-value` — kobler fane og panel eksplisitt, uavhengig av
80
+ * DOM-rekkefølge (React setter dette; ren HTML kan også sette det).
81
+ * 3. Posisjon (fane[i] → panel[i]) som siste utvei for enkel markup.
82
+ */
83
+ private _getPanel;
84
+ /**
85
+ * Kobler roller, IDer og kryssreferanser, og setter initial valgt fane.
86
+ * Idempotent — kan kjøres på nytt når DOM endres.
87
+ */
88
+ private _wire;
89
+ /**
90
+ * Aktiverer en fane: flytter aria-selected + roving tabindex hit, viser dens
91
+ * panel og skjuler de øvrige. Sender kun change ved ekte brukerinteraksjon
92
+ * (dispatch=true) OG når valget faktisk endres.
93
+ */
94
+ setSelected(selected: HTMLElement | null | undefined, dispatch?: boolean): void;
95
+ /**
96
+ * Vis/skjul et panel. Skjult panel fjernes fra a11y-treet og lesefokus.
97
+ * Panelet gjøres ikke fokuserbart — det inneholder eget innhold (tekst,
98
+ * lenker, felt) og skal ikke være en egen tab-stopp. Har panelet ikke
99
+ * fokuserbart innhold, klarer skjermlesere seg med aria-labelledby-koblingen.
100
+ */
101
+ private _syncPanel;
102
+ /** Flytt fokus (roving tabindex) til en fane uten å aktivere den. */
103
+ private _moveFocus;
104
+ /** Neste fane fra `from`, i retning `step`, med loop rundt endene. */
105
+ private _step;
106
+ private _onClick;
107
+ private _onKeydown;
108
+ private _onMutations;
109
+ }
110
+ /** role="tablist" + horisontal orientering. Koordineres av <ix-tabs>. */
111
+ export declare class IxTabList extends HTMLElement {
112
+ connectedCallback(): void;
113
+ }
114
+ /** role="tab". Aktiv-tilstand og tabindex styres av <ix-tabs>. */
115
+ export declare class IxTab extends HTMLElement {
116
+ connectedCallback(): void;
117
+ }
118
+ /** role="tabpanel". Vis/skjul styres av <ix-tabs>. */
119
+ export declare class IxTabPanel extends HTMLElement {
120
+ connectedCallback(): void;
121
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Plattform-deteksjon for web components.
3
+ *
4
+ * Funksjoner (ikke modul-konstanter) fordi userAgent leses ved kall — det gjør
5
+ * grenene testbare via stub av `navigator.userAgent`, og unngår å cache en verdi
6
+ * ved import-tid som kan være feil i SSR-/testmiljø.
7
+ */
8
+ /**
9
+ * True når koden kjører på iOS (iPhone/iPad/iPod).
10
+ *
11
+ * Brukes til iOS-spesifikke a11y-workarounds, f.eks. at VoiceOver ikke leser
12
+ * løpende `aria-valuenow`-endringer på `role="progressbar"` — se IxProgressBar.
13
+ * SSR-guardet: returnerer false når `navigator` ikke finnes.
14
+ */
15
+ export declare function isIOS(): boolean;