@sb1/indeks-web 0.18.1 → 0.20.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
CHANGED
|
@@ -1,5 +1,28 @@
|
|
|
1
1
|
# @sb1/indeks-web
|
|
2
2
|
|
|
3
|
+
## 0.20.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- a64a806: Ny Popover-komponent for kontekstuell informasjon og handlinger
|
|
8
|
+
|
|
9
|
+
- CSS: `.ix-popover`, `.ix-popover__content`, `.ix-popover__heading`, `.ix-popover__body`, `.ix-popover__actions`
|
|
10
|
+
- Web component: `<ix-popover>` med ARIA-lim, posisjonering og fokushåndtering
|
|
11
|
+
- React: `<Popover>` med sub-komponenter `Popover.Trigger`, `Popover.Content`, `Popover.Heading`, `Popover.Body`, `Popover.Actions`
|
|
12
|
+
|
|
13
|
+
### Patch Changes
|
|
14
|
+
|
|
15
|
+
- b71ecc3: Fiks heading i Popover og åpning av submenyer på hover i DropdownMenu
|
|
16
|
+
|
|
17
|
+
- Popover: Heading er nå stylet som en overskrift
|
|
18
|
+
- DropdownMenu: Submenyer åpnes nå ved hover i tillegg til tastatur (ArrowRight)
|
|
19
|
+
|
|
20
|
+
## 0.19.0
|
|
21
|
+
|
|
22
|
+
### Minor Changes
|
|
23
|
+
|
|
24
|
+
- ef4f6e2: Ny DropdownMenu-komponent for handlingsmenyer og navigasjon. Komponenten følger APG Menu Button-mønsteret med full tastaturstøtte (piltaster, Home/End, Escape, typeahead), submenyer, og automatisk ARIA-oppsett. CSS-laget definerer `.ix-dropdown__menu`, `.ix-dropdown__item`, `.ix-dropdown__divider` og `.ix-dropdown__group`. Web component `<ix-dropdown>` håndterer åpne/lukke, posisjonering og fokusstyring. React-wrapperen `<DropdownMenu>` eksponerer `<DropdownMenu.Trigger>`, `<DropdownMenu.Menu>`, `<DropdownMenu.Item>`, `<DropdownMenu.Divider>` og `<DropdownMenu.Group>`.
|
|
25
|
+
|
|
3
26
|
## 0.18.1
|
|
4
27
|
|
|
5
28
|
## 0.18.0
|
package/dist/npm/index.d.ts
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import { IxCheckboxGroup } from './lib/components/checkbox-group/IxCheckboxGroup.js';
|
|
2
2
|
import { IxCombobox } from './lib/components/combobox/IxCombobox.js';
|
|
3
3
|
import { IxDateField } from './lib/components/date-field/IxDateField.js';
|
|
4
|
+
import { IxDropdown } from './lib/components/dropdown/IxDropdown.js';
|
|
4
5
|
import { IxField } from './lib/components/field/IxField.js';
|
|
5
6
|
import { IxIcon, IconName } from './lib/components/icon/IxIcon.js';
|
|
6
7
|
import { IxPhoneNumberField } from './lib/components/phone-number-field/IxPhoneNumberField.js';
|
|
8
|
+
import { IxPopover } from './lib/components/popover/IxPopover.js';
|
|
7
9
|
import { IxProgressBar } from './lib/components/progress-bar/IxProgressBar.js';
|
|
8
10
|
import { IxRadioGroup } from './lib/components/radio-group/IxRadioGroup.js';
|
|
9
11
|
import { IxTabs, IxTabList, IxTab, IxTabPanel } from './lib/components/tabs/IxTabs.js';
|
|
@@ -13,7 +15,9 @@ export { IxRadioGroup };
|
|
|
13
15
|
export { IxCheckboxGroup };
|
|
14
16
|
export { IxCombobox };
|
|
15
17
|
export { IxDateField };
|
|
18
|
+
export { IxDropdown };
|
|
16
19
|
export { IxPhoneNumberField };
|
|
20
|
+
export { IxPopover };
|
|
17
21
|
export { IxProgressBar };
|
|
18
22
|
export type { ProgressBarState } from './lib/components/progress-bar/IxProgressBar.js';
|
|
19
23
|
export { IxTabs, IxTabList, IxTab, IxTabPanel };
|
|
@@ -32,7 +36,9 @@ declare global {
|
|
|
32
36
|
'ix-checkbox-group': IxCheckboxGroup;
|
|
33
37
|
'ix-combobox': IxCombobox;
|
|
34
38
|
'ix-date-field': IxDateField;
|
|
39
|
+
'ix-dropdown': IxDropdown;
|
|
35
40
|
'ix-phone-number-field': IxPhoneNumberField;
|
|
41
|
+
'ix-popover': IxPopover;
|
|
36
42
|
'ix-progress-bar': IxProgressBar;
|
|
37
43
|
'ix-tabs': IxTabs;
|
|
38
44
|
'ix-tab-list': IxTabList;
|
|
@@ -101,6 +107,19 @@ declare global {
|
|
|
101
107
|
class?: string;
|
|
102
108
|
children?: unknown;
|
|
103
109
|
};
|
|
110
|
+
'ix-dropdown': {
|
|
111
|
+
open?: boolean | '';
|
|
112
|
+
placement?: 'bottom-start' | 'bottom-end' | 'top-start' | 'top-end';
|
|
113
|
+
'data-submenu'?: boolean | '';
|
|
114
|
+
};
|
|
115
|
+
'ix-popover': {
|
|
116
|
+
open?: boolean | '';
|
|
117
|
+
placement?: 'top' | 'bottom' | 'left' | 'right';
|
|
118
|
+
'data-arrow'?: 'true' | 'false';
|
|
119
|
+
'data-controlled'?: boolean | '';
|
|
120
|
+
class?: string;
|
|
121
|
+
children?: unknown;
|
|
122
|
+
};
|
|
104
123
|
'ix-icon': {
|
|
105
124
|
name?: IconName;
|
|
106
125
|
'data-size'?: 'sm' | 'md' | 'lg' | 'xl';
|
package/dist/npm/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
//#region \0@oxc-project+runtime@0.
|
|
1
|
+
//#region \0@oxc-project+runtime@0.144.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) {
|
|
@@ -8,7 +8,7 @@ function _typeof(o) {
|
|
|
8
8
|
}, _typeof(o);
|
|
9
9
|
}
|
|
10
10
|
//#endregion
|
|
11
|
-
//#region \0@oxc-project+runtime@0.
|
|
11
|
+
//#region \0@oxc-project+runtime@0.144.0/helpers/esm/toPrimitive.js
|
|
12
12
|
function toPrimitive(t, r) {
|
|
13
13
|
if ("object" != _typeof(t) || !t) return t;
|
|
14
14
|
var e = t[Symbol.toPrimitive];
|
|
@@ -20,13 +20,13 @@ function toPrimitive(t, r) {
|
|
|
20
20
|
return ("string" === r ? String : Number)(t);
|
|
21
21
|
}
|
|
22
22
|
//#endregion
|
|
23
|
-
//#region \0@oxc-project+runtime@0.
|
|
23
|
+
//#region \0@oxc-project+runtime@0.144.0/helpers/esm/toPropertyKey.js
|
|
24
24
|
function toPropertyKey(t) {
|
|
25
25
|
var i = toPrimitive(t, "string");
|
|
26
26
|
return "symbol" == _typeof(i) ? i : i + "";
|
|
27
27
|
}
|
|
28
28
|
//#endregion
|
|
29
|
-
//#region \0@oxc-project+runtime@0.
|
|
29
|
+
//#region \0@oxc-project+runtime@0.144.0/helpers/esm/defineProperty.js
|
|
30
30
|
function _defineProperty(e, r, t) {
|
|
31
31
|
return (r = toPropertyKey(r)) in e ? Object.defineProperty(e, r, {
|
|
32
32
|
value: t,
|
|
@@ -506,7 +506,8 @@ var IxCombobox = class extends HTMLElement {
|
|
|
506
506
|
listbox.style.left = `${Math.max(8, r.left)}px`;
|
|
507
507
|
const ph = listbox.offsetHeight;
|
|
508
508
|
const spaceBelow = vh - r.bottom;
|
|
509
|
-
|
|
509
|
+
const spaceAbove = r.top;
|
|
510
|
+
if (ph > spaceBelow && spaceAbove > spaceBelow) listbox.style.top = `${Math.max(8, r.top - ph - gap)}px`;
|
|
510
511
|
else listbox.style.top = `${r.bottom + gap}px`;
|
|
511
512
|
}
|
|
512
513
|
_addRepositionListeners() {
|
|
@@ -619,16 +620,14 @@ var IxCombobox = class extends HTMLElement {
|
|
|
619
620
|
}
|
|
620
621
|
break;
|
|
621
622
|
case "ArrowLeft":
|
|
622
|
-
case "Backspace":
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
last.focus();
|
|
629
|
-
}
|
|
623
|
+
case "Backspace": if (this.multiple && input.selectionStart === 0 && input.selectionEnd === 0) {
|
|
624
|
+
const chips = this._chipElements();
|
|
625
|
+
const last = chips[chips.length - 1];
|
|
626
|
+
if (last) {
|
|
627
|
+
e.preventDefault();
|
|
628
|
+
last.focus();
|
|
630
629
|
}
|
|
631
|
-
|
|
630
|
+
}
|
|
632
631
|
}
|
|
633
632
|
}
|
|
634
633
|
_handleChipKeydown(e, chip) {
|
|
@@ -1321,6 +1320,503 @@ var IxDateField = class extends HTMLElement {
|
|
|
1321
1320
|
}
|
|
1322
1321
|
};
|
|
1323
1322
|
//#endregion
|
|
1323
|
+
//#region lib/components/dropdown/IxDropdown.ts
|
|
1324
|
+
/**
|
|
1325
|
+
* IxDropdown — ARIA-lim for en nedtrekksmeny med handlinger eller navigasjon.
|
|
1326
|
+
*
|
|
1327
|
+
* ## Designfilosofi
|
|
1328
|
+
*
|
|
1329
|
+
* Komponenten kobler en trigger-knapp med en meny. Forfatteren skriver en
|
|
1330
|
+
* <button> (trigger) og en .ix-dropdown__menu med .ix-dropdown__item-elementer
|
|
1331
|
+
* i light DOM. Komponentens jobb er å sette opp ARIA-attributter, håndtere
|
|
1332
|
+
* tastaturnavigasjon, posisjonere menyen, og styre åpen/lukket-tilstand.
|
|
1333
|
+
*
|
|
1334
|
+
* ## Roving tabindex
|
|
1335
|
+
*
|
|
1336
|
+
* Alle items har tabindex="-1" unntatt det aktive (fokuserte). Piltaster
|
|
1337
|
+
* flytter fokus mellom items. Dette følger APG Menu Button-mønsteret.
|
|
1338
|
+
*
|
|
1339
|
+
* ## Submenyer
|
|
1340
|
+
*
|
|
1341
|
+
* En submenu er en nøstet <ix-dropdown data-submenu> inne i parent-menyen.
|
|
1342
|
+
* ArrowRight på en submenu-trigger åpner submenyen, ArrowLeft lukker den.
|
|
1343
|
+
* Submenyen posisjoneres til høyre for parent-item (flipper ved plassmangel).
|
|
1344
|
+
*
|
|
1345
|
+
* ## Posisjonering
|
|
1346
|
+
*
|
|
1347
|
+
* Menyen posisjoneres manuelt med getBoundingClientRect + viewport-clamp
|
|
1348
|
+
* (samme grep som tooltip og combobox) — position: fixed, JS setter top/left.
|
|
1349
|
+
*
|
|
1350
|
+
* @example
|
|
1351
|
+
* <ix-dropdown>
|
|
1352
|
+
* <button class="ix-button" data-variant="secondary">Handlinger</button>
|
|
1353
|
+
* <div class="ix-dropdown__menu">
|
|
1354
|
+
* <button class="ix-dropdown__item">Rediger</button>
|
|
1355
|
+
* <button class="ix-dropdown__item">Dupliser</button>
|
|
1356
|
+
* <hr class="ix-dropdown__divider" />
|
|
1357
|
+
* <button class="ix-dropdown__item" data-danger>Slett</button>
|
|
1358
|
+
* </div>
|
|
1359
|
+
* </ix-dropdown>
|
|
1360
|
+
*/
|
|
1361
|
+
var dropdownCounter = 0;
|
|
1362
|
+
var IxDropdown = class extends HTMLElement {
|
|
1363
|
+
constructor(..._args) {
|
|
1364
|
+
super(..._args);
|
|
1365
|
+
_defineProperty(this, "_instanceId", 0);
|
|
1366
|
+
_defineProperty(this, "_trigger", null);
|
|
1367
|
+
_defineProperty(this, "_menu", null);
|
|
1368
|
+
_defineProperty(this, "_items", []);
|
|
1369
|
+
_defineProperty(this, "_activeIndex", -1);
|
|
1370
|
+
_defineProperty(this, "_onTriggerClick", null);
|
|
1371
|
+
_defineProperty(this, "_onTriggerKeydown", null);
|
|
1372
|
+
_defineProperty(this, "_onMenuKeydown", null);
|
|
1373
|
+
_defineProperty(this, "_onMenuClick", null);
|
|
1374
|
+
_defineProperty(this, "_onMenuPointerenter", null);
|
|
1375
|
+
_defineProperty(this, "_onDocPointer", null);
|
|
1376
|
+
_defineProperty(this, "_onFocusout", null);
|
|
1377
|
+
_defineProperty(this, "_onReposition", null);
|
|
1378
|
+
_defineProperty(this, "_itemObserver", null);
|
|
1379
|
+
_defineProperty(this, "_typeaheadBuffer", "");
|
|
1380
|
+
_defineProperty(this, "_typeaheadTimer", null);
|
|
1381
|
+
_defineProperty(this, "_openedWithKeyboard", false);
|
|
1382
|
+
}
|
|
1383
|
+
static get observedAttributes() {
|
|
1384
|
+
return [
|
|
1385
|
+
"open",
|
|
1386
|
+
"placement",
|
|
1387
|
+
"data-controlled"
|
|
1388
|
+
];
|
|
1389
|
+
}
|
|
1390
|
+
/**
|
|
1391
|
+
* Kontrollert modus: når `data-controlled` er satt, endrer ikke komponenten
|
|
1392
|
+
* egen åpen-tilstand. Den dispatcher kun `open-request` / `close-request`,
|
|
1393
|
+
* og foreldren må sette `open`-attributtet for å faktisk åpne/lukke.
|
|
1394
|
+
*/
|
|
1395
|
+
get _isControlled() {
|
|
1396
|
+
return this.hasAttribute("data-controlled");
|
|
1397
|
+
}
|
|
1398
|
+
connectedCallback() {
|
|
1399
|
+
this._instanceId = ++dropdownCounter;
|
|
1400
|
+
this._wire();
|
|
1401
|
+
}
|
|
1402
|
+
disconnectedCallback() {
|
|
1403
|
+
if (this._trigger) {
|
|
1404
|
+
if (this._onTriggerClick) this._trigger.removeEventListener("click", this._onTriggerClick);
|
|
1405
|
+
if (this._onTriggerKeydown) this._trigger.removeEventListener("keydown", this._onTriggerKeydown);
|
|
1406
|
+
}
|
|
1407
|
+
if (this._menu) {
|
|
1408
|
+
if (this._onMenuKeydown) this._menu.removeEventListener("keydown", this._onMenuKeydown);
|
|
1409
|
+
if (this._onMenuClick) this._menu.removeEventListener("click", this._onMenuClick);
|
|
1410
|
+
if (this._onMenuPointerenter) this._menu.removeEventListener("pointerenter", this._onMenuPointerenter, true);
|
|
1411
|
+
}
|
|
1412
|
+
if (this._onDocPointer) document.removeEventListener("pointerdown", this._onDocPointer, true);
|
|
1413
|
+
if (this._onFocusout) this.removeEventListener("focusout", this._onFocusout);
|
|
1414
|
+
this._removeRepositionListeners();
|
|
1415
|
+
this._itemObserver?.disconnect();
|
|
1416
|
+
if (this._typeaheadTimer) {
|
|
1417
|
+
clearTimeout(this._typeaheadTimer);
|
|
1418
|
+
this._typeaheadTimer = null;
|
|
1419
|
+
}
|
|
1420
|
+
this._onTriggerClick = null;
|
|
1421
|
+
this._onTriggerKeydown = null;
|
|
1422
|
+
this._onMenuKeydown = null;
|
|
1423
|
+
this._onMenuClick = null;
|
|
1424
|
+
this._onMenuPointerenter = null;
|
|
1425
|
+
this._onDocPointer = null;
|
|
1426
|
+
this._onFocusout = null;
|
|
1427
|
+
this._onReposition = null;
|
|
1428
|
+
this._itemObserver = null;
|
|
1429
|
+
this._trigger = null;
|
|
1430
|
+
this._menu = null;
|
|
1431
|
+
this._items = [];
|
|
1432
|
+
}
|
|
1433
|
+
attributeChangedCallback(name, _oldValue, newValue) {
|
|
1434
|
+
if (!this.isConnected) return;
|
|
1435
|
+
if (name === "open") {
|
|
1436
|
+
const shouldBeOpen = newValue !== null && newValue !== "false";
|
|
1437
|
+
if (shouldBeOpen && !this._isOpen) this._doOpen();
|
|
1438
|
+
else if (!shouldBeOpen && this._isOpen) this._doClose();
|
|
1439
|
+
} else if (name === "placement" && this._isOpen) this._position();
|
|
1440
|
+
}
|
|
1441
|
+
get open() {
|
|
1442
|
+
const attr = this.getAttribute("open");
|
|
1443
|
+
return attr !== null && attr !== "false";
|
|
1444
|
+
}
|
|
1445
|
+
set open(value) {
|
|
1446
|
+
this.toggleAttribute("open", value);
|
|
1447
|
+
}
|
|
1448
|
+
get _isOpen() {
|
|
1449
|
+
return this.hasAttribute("data-open");
|
|
1450
|
+
}
|
|
1451
|
+
get _isSubmenu() {
|
|
1452
|
+
return this.hasAttribute("data-submenu");
|
|
1453
|
+
}
|
|
1454
|
+
get _placement() {
|
|
1455
|
+
return this.getAttribute("placement") ?? "bottom-start";
|
|
1456
|
+
}
|
|
1457
|
+
_wire() {
|
|
1458
|
+
this._menu = this.querySelector(":scope > .ix-dropdown__menu");
|
|
1459
|
+
if (this._isSubmenu) this._trigger = this.querySelector(":scope > .ix-dropdown__item");
|
|
1460
|
+
else this._trigger = Array.from(this.children).find((child) => child instanceof HTMLElement && !child.classList.contains("ix-dropdown__menu")) ?? null;
|
|
1461
|
+
if (!this._trigger || !this._menu) return;
|
|
1462
|
+
if (!this._menu.id) this._menu.id = `ix-dropdown-menu-${this._instanceId}`;
|
|
1463
|
+
if (!this._menu.getAttribute("role")) this._menu.setAttribute("role", "menu");
|
|
1464
|
+
this._menu.hidden = true;
|
|
1465
|
+
this._trigger.setAttribute("aria-haspopup", "menu");
|
|
1466
|
+
this._trigger.setAttribute("aria-expanded", "false");
|
|
1467
|
+
this._trigger.setAttribute("aria-controls", this._menu.id);
|
|
1468
|
+
this._wireItems();
|
|
1469
|
+
this._onTriggerClick = (e) => {
|
|
1470
|
+
e.preventDefault();
|
|
1471
|
+
e.stopPropagation();
|
|
1472
|
+
this._openedWithKeyboard = false;
|
|
1473
|
+
this._toggleOpen();
|
|
1474
|
+
};
|
|
1475
|
+
this._trigger.addEventListener("click", this._onTriggerClick);
|
|
1476
|
+
this._onTriggerKeydown = (e) => this._handleTriggerKeydown(e);
|
|
1477
|
+
this._trigger.addEventListener("keydown", this._onTriggerKeydown);
|
|
1478
|
+
this._onMenuKeydown = (e) => this._handleMenuKeydown(e);
|
|
1479
|
+
this._menu.addEventListener("keydown", this._onMenuKeydown);
|
|
1480
|
+
this._onMenuClick = (e) => {
|
|
1481
|
+
const item = e.target.closest(".ix-dropdown__item");
|
|
1482
|
+
if (item && this._menu?.contains(item)) this._handleItemClick(e, item);
|
|
1483
|
+
};
|
|
1484
|
+
this._menu.addEventListener("click", this._onMenuClick);
|
|
1485
|
+
this._onMenuPointerenter = (e) => {
|
|
1486
|
+
const item = e.target.closest(".ix-dropdown__item");
|
|
1487
|
+
if (item && this._menu?.contains(item)) {
|
|
1488
|
+
const idx = this._items.indexOf(item);
|
|
1489
|
+
if (idx !== -1) this._handleItemPointerEnter(idx);
|
|
1490
|
+
}
|
|
1491
|
+
};
|
|
1492
|
+
this._menu.addEventListener("pointerenter", this._onMenuPointerenter, true);
|
|
1493
|
+
this._onDocPointer = (e) => {
|
|
1494
|
+
if (!this._isOpen) return;
|
|
1495
|
+
const target = e.target;
|
|
1496
|
+
if (!this.contains(target)) this._close();
|
|
1497
|
+
};
|
|
1498
|
+
document.addEventListener("pointerdown", this._onDocPointer, true);
|
|
1499
|
+
this._onFocusout = (e) => {
|
|
1500
|
+
if (!this._isOpen) return;
|
|
1501
|
+
const relatedTarget = e.relatedTarget;
|
|
1502
|
+
if (relatedTarget && this.contains(relatedTarget)) return;
|
|
1503
|
+
const rootDropdown = this.closest("ix-dropdown:not([data-submenu])") ?? this;
|
|
1504
|
+
if (relatedTarget && rootDropdown.contains(relatedTarget)) return;
|
|
1505
|
+
this._close();
|
|
1506
|
+
};
|
|
1507
|
+
this.addEventListener("focusout", this._onFocusout);
|
|
1508
|
+
this._itemObserver = new MutationObserver(() => this._rewireItems());
|
|
1509
|
+
this._itemObserver.observe(this._menu, {
|
|
1510
|
+
childList: true,
|
|
1511
|
+
subtree: true
|
|
1512
|
+
});
|
|
1513
|
+
if (this.open) this._doOpen();
|
|
1514
|
+
}
|
|
1515
|
+
_wireItems() {
|
|
1516
|
+
this._rewireItems();
|
|
1517
|
+
}
|
|
1518
|
+
_rewireItems() {
|
|
1519
|
+
if (!this._menu) return;
|
|
1520
|
+
const items = Array.from(this._menu.children).filter((child) => child instanceof HTMLElement && child.classList.contains("ix-dropdown__item"));
|
|
1521
|
+
Array.from(this._menu.querySelectorAll(":scope > ix-dropdown[data-submenu]")).forEach((submenu) => {
|
|
1522
|
+
const submenuTrigger = submenu.querySelector(":scope > .ix-dropdown__item");
|
|
1523
|
+
if (submenuTrigger) {
|
|
1524
|
+
items.push(submenuTrigger);
|
|
1525
|
+
submenuTrigger.setAttribute("aria-haspopup", "menu");
|
|
1526
|
+
submenuTrigger.setAttribute("aria-expanded", "false");
|
|
1527
|
+
}
|
|
1528
|
+
});
|
|
1529
|
+
items.sort((a, b) => {
|
|
1530
|
+
const position = a.compareDocumentPosition(b);
|
|
1531
|
+
if (position & Node.DOCUMENT_POSITION_FOLLOWING) return -1;
|
|
1532
|
+
if (position & Node.DOCUMENT_POSITION_PRECEDING) return 1;
|
|
1533
|
+
return 0;
|
|
1534
|
+
});
|
|
1535
|
+
items.forEach((item) => {
|
|
1536
|
+
if (!item.getAttribute("role")) item.setAttribute("role", "menuitem");
|
|
1537
|
+
item.setAttribute("tabindex", "-1");
|
|
1538
|
+
});
|
|
1539
|
+
this._menu.querySelectorAll(":scope > .ix-dropdown__divider").forEach((divider) => {
|
|
1540
|
+
if (!divider.getAttribute("role")) divider.setAttribute("role", "separator");
|
|
1541
|
+
});
|
|
1542
|
+
this._items = items;
|
|
1543
|
+
}
|
|
1544
|
+
_handleTriggerKeydown(e) {
|
|
1545
|
+
if (this._isSubmenu) return;
|
|
1546
|
+
switch (e.key) {
|
|
1547
|
+
case "Enter":
|
|
1548
|
+
case " ":
|
|
1549
|
+
case "ArrowDown":
|
|
1550
|
+
e.preventDefault();
|
|
1551
|
+
this._openedWithKeyboard = true;
|
|
1552
|
+
this._open();
|
|
1553
|
+
break;
|
|
1554
|
+
case "ArrowUp":
|
|
1555
|
+
e.preventDefault();
|
|
1556
|
+
this._openedWithKeyboard = true;
|
|
1557
|
+
this._open();
|
|
1558
|
+
this._focusItem(this._items.length - 1);
|
|
1559
|
+
}
|
|
1560
|
+
}
|
|
1561
|
+
_handleMenuKeydown(e) {
|
|
1562
|
+
const target = e.target;
|
|
1563
|
+
if (!this._menu?.contains(target)) return;
|
|
1564
|
+
const closestSubmenuMenu = target.closest("ix-dropdown[data-submenu] > .ix-dropdown__menu");
|
|
1565
|
+
if (closestSubmenuMenu && closestSubmenuMenu !== this._menu && this._menu.contains(closestSubmenuMenu)) return;
|
|
1566
|
+
switch (e.key) {
|
|
1567
|
+
case "Escape":
|
|
1568
|
+
e.preventDefault();
|
|
1569
|
+
e.stopPropagation();
|
|
1570
|
+
this._close();
|
|
1571
|
+
this._trigger?.focus();
|
|
1572
|
+
break;
|
|
1573
|
+
case "ArrowDown":
|
|
1574
|
+
e.preventDefault();
|
|
1575
|
+
this._focusNextItem(1);
|
|
1576
|
+
break;
|
|
1577
|
+
case "ArrowUp":
|
|
1578
|
+
e.preventDefault();
|
|
1579
|
+
this._focusNextItem(-1);
|
|
1580
|
+
break;
|
|
1581
|
+
case "Home":
|
|
1582
|
+
e.preventDefault();
|
|
1583
|
+
this._focusItem(0);
|
|
1584
|
+
break;
|
|
1585
|
+
case "End":
|
|
1586
|
+
e.preventDefault();
|
|
1587
|
+
this._focusItem(this._items.length - 1);
|
|
1588
|
+
break;
|
|
1589
|
+
case "ArrowRight": {
|
|
1590
|
+
const submenu = target.closest("ix-dropdown[data-submenu]");
|
|
1591
|
+
if (submenu && submenu !== this && target === submenu._trigger) {
|
|
1592
|
+
e.preventDefault();
|
|
1593
|
+
submenu._openedWithKeyboard = true;
|
|
1594
|
+
submenu._open();
|
|
1595
|
+
}
|
|
1596
|
+
break;
|
|
1597
|
+
}
|
|
1598
|
+
case "ArrowLeft":
|
|
1599
|
+
if (this._isSubmenu) {
|
|
1600
|
+
e.preventDefault();
|
|
1601
|
+
e.stopPropagation();
|
|
1602
|
+
const parentDropdown = this.parentElement?.closest("ix-dropdown");
|
|
1603
|
+
if (this._trigger) {
|
|
1604
|
+
this._trigger.focus();
|
|
1605
|
+
if (parentDropdown) {
|
|
1606
|
+
const triggerIndex = parentDropdown._items.indexOf(this._trigger);
|
|
1607
|
+
if (triggerIndex !== -1) parentDropdown._focusItem(triggerIndex);
|
|
1608
|
+
}
|
|
1609
|
+
}
|
|
1610
|
+
this._close();
|
|
1611
|
+
}
|
|
1612
|
+
break;
|
|
1613
|
+
case "Enter":
|
|
1614
|
+
case " ":
|
|
1615
|
+
if (target.classList.contains("ix-dropdown__item")) {
|
|
1616
|
+
e.preventDefault();
|
|
1617
|
+
const submenu = target.parentElement;
|
|
1618
|
+
if (submenu?.hasAttribute("data-submenu") && target === submenu._trigger) {
|
|
1619
|
+
submenu._openedWithKeyboard = true;
|
|
1620
|
+
submenu._open();
|
|
1621
|
+
} else target.click();
|
|
1622
|
+
}
|
|
1623
|
+
break;
|
|
1624
|
+
case "Tab":
|
|
1625
|
+
e.preventDefault();
|
|
1626
|
+
this._closeAll();
|
|
1627
|
+
break;
|
|
1628
|
+
default: if (e.key.length === 1 && !e.ctrlKey && !e.metaKey) this._handleTypeahead(e.key);
|
|
1629
|
+
}
|
|
1630
|
+
}
|
|
1631
|
+
_handleItemPointerEnter(index) {
|
|
1632
|
+
this._activeIndex = index;
|
|
1633
|
+
if (this._menu?.contains(document.activeElement)) this._focusItem(index);
|
|
1634
|
+
const item = this._items[index];
|
|
1635
|
+
if (!item) return;
|
|
1636
|
+
const submenu = item.parentElement;
|
|
1637
|
+
if (submenu?.hasAttribute("data-submenu") && item === submenu._trigger) {
|
|
1638
|
+
this._closeOtherSubmenus(submenu);
|
|
1639
|
+
submenu._open();
|
|
1640
|
+
} else this._closeAllSubmenus();
|
|
1641
|
+
}
|
|
1642
|
+
_closeOtherSubmenus(keep) {
|
|
1643
|
+
(this._menu?.querySelectorAll("ix-dropdown[data-submenu]"))?.forEach((submenu) => {
|
|
1644
|
+
if (submenu !== keep && submenu._isOpen) submenu._close();
|
|
1645
|
+
});
|
|
1646
|
+
}
|
|
1647
|
+
_handleItemClick(e, item) {
|
|
1648
|
+
if (item.hasAttribute("disabled") || item.getAttribute("aria-disabled") === "true") {
|
|
1649
|
+
e.preventDefault();
|
|
1650
|
+
return;
|
|
1651
|
+
}
|
|
1652
|
+
const submenu = item.parentElement;
|
|
1653
|
+
if (submenu?.hasAttribute("data-submenu") && item === submenu._trigger) {
|
|
1654
|
+
e.stopPropagation();
|
|
1655
|
+
return;
|
|
1656
|
+
}
|
|
1657
|
+
this._activeIndex = this._items.indexOf(item);
|
|
1658
|
+
this._closeAll();
|
|
1659
|
+
}
|
|
1660
|
+
_handleTypeahead(char) {
|
|
1661
|
+
this._typeaheadBuffer += char.toLowerCase();
|
|
1662
|
+
if (this._typeaheadTimer) clearTimeout(this._typeaheadTimer);
|
|
1663
|
+
this._typeaheadTimer = setTimeout(() => {
|
|
1664
|
+
this._typeaheadBuffer = "";
|
|
1665
|
+
}, 500);
|
|
1666
|
+
const match = this._items.findIndex((item) => {
|
|
1667
|
+
if (item.hasAttribute("disabled") || item.getAttribute("aria-disabled") === "true") return false;
|
|
1668
|
+
return (item.textContent ?? "").trim().toLowerCase().startsWith(this._typeaheadBuffer);
|
|
1669
|
+
});
|
|
1670
|
+
if (match !== -1) this._focusItem(match);
|
|
1671
|
+
}
|
|
1672
|
+
_focusItem(index) {
|
|
1673
|
+
if (this._items.filter((item) => !item.hasAttribute("disabled") && item.getAttribute("aria-disabled") !== "true").length === 0) return;
|
|
1674
|
+
const clampedIndex = Math.max(0, Math.min(index, this._items.length - 1));
|
|
1675
|
+
const item = this._items[clampedIndex];
|
|
1676
|
+
if (item?.hasAttribute("disabled") || item?.getAttribute("aria-disabled") === "true") {
|
|
1677
|
+
this._focusNextItem(1);
|
|
1678
|
+
return;
|
|
1679
|
+
}
|
|
1680
|
+
this._items.forEach((i, idx) => {
|
|
1681
|
+
i.setAttribute("tabindex", idx === clampedIndex ? "0" : "-1");
|
|
1682
|
+
i.removeAttribute("data-active");
|
|
1683
|
+
});
|
|
1684
|
+
if (item) {
|
|
1685
|
+
item.setAttribute("data-active", "");
|
|
1686
|
+
item.focus();
|
|
1687
|
+
this._activeIndex = clampedIndex;
|
|
1688
|
+
}
|
|
1689
|
+
}
|
|
1690
|
+
_focusNextItem(delta) {
|
|
1691
|
+
if (this._items.filter((item) => !item.hasAttribute("disabled") && item.getAttribute("aria-disabled") !== "true").length === 0) return;
|
|
1692
|
+
let currentIdx = this._activeIndex;
|
|
1693
|
+
if (currentIdx === -1) currentIdx = delta > 0 ? -1 : this._items.length;
|
|
1694
|
+
let nextIdx = currentIdx;
|
|
1695
|
+
let iterations = 0;
|
|
1696
|
+
do {
|
|
1697
|
+
nextIdx = nextIdx + delta;
|
|
1698
|
+
if (nextIdx < 0) nextIdx = this._items.length - 1;
|
|
1699
|
+
if (nextIdx >= this._items.length) nextIdx = 0;
|
|
1700
|
+
iterations++;
|
|
1701
|
+
} while (iterations < this._items.length && (this._items[nextIdx]?.hasAttribute("disabled") || this._items[nextIdx]?.getAttribute("aria-disabled") === "true"));
|
|
1702
|
+
this._focusItem(nextIdx);
|
|
1703
|
+
}
|
|
1704
|
+
_open() {
|
|
1705
|
+
if (this._isOpen || !this._menu || !this._trigger) return;
|
|
1706
|
+
if (this._isControlled) {
|
|
1707
|
+
this.dispatchEvent(new CustomEvent("open-request", { bubbles: true }));
|
|
1708
|
+
return;
|
|
1709
|
+
}
|
|
1710
|
+
this._doOpen();
|
|
1711
|
+
}
|
|
1712
|
+
_doOpen() {
|
|
1713
|
+
if (this._isOpen || !this._menu || !this._trigger) return;
|
|
1714
|
+
this.setAttribute("data-open", "");
|
|
1715
|
+
this.setAttribute("open", "");
|
|
1716
|
+
this._menu.hidden = false;
|
|
1717
|
+
this._trigger.setAttribute("aria-expanded", "true");
|
|
1718
|
+
this._position();
|
|
1719
|
+
this._addRepositionListeners();
|
|
1720
|
+
if (this._openedWithKeyboard) this._focusItem(0);
|
|
1721
|
+
this.dispatchEvent(new CustomEvent("open", { bubbles: true }));
|
|
1722
|
+
}
|
|
1723
|
+
_close() {
|
|
1724
|
+
if (!this._isOpen || !this._menu || !this._trigger) return;
|
|
1725
|
+
if (this._isControlled) {
|
|
1726
|
+
this.dispatchEvent(new CustomEvent("close-request", { bubbles: true }));
|
|
1727
|
+
return;
|
|
1728
|
+
}
|
|
1729
|
+
this._doClose();
|
|
1730
|
+
}
|
|
1731
|
+
_doClose() {
|
|
1732
|
+
if (!this._isOpen || !this._menu || !this._trigger) return;
|
|
1733
|
+
this._closeAllSubmenus();
|
|
1734
|
+
this.removeAttribute("data-open");
|
|
1735
|
+
this.removeAttribute("open");
|
|
1736
|
+
this._menu.hidden = true;
|
|
1737
|
+
this._trigger.setAttribute("aria-expanded", "false");
|
|
1738
|
+
this._items.forEach((item) => {
|
|
1739
|
+
item.setAttribute("tabindex", "-1");
|
|
1740
|
+
item.removeAttribute("data-active");
|
|
1741
|
+
});
|
|
1742
|
+
this._activeIndex = -1;
|
|
1743
|
+
this._removeRepositionListeners();
|
|
1744
|
+
this.dispatchEvent(new CustomEvent("close", { bubbles: true }));
|
|
1745
|
+
}
|
|
1746
|
+
_toggleOpen() {
|
|
1747
|
+
if (this._isOpen) this._close();
|
|
1748
|
+
else this._open();
|
|
1749
|
+
}
|
|
1750
|
+
_closeAll() {
|
|
1751
|
+
let root = this.parentElement?.closest("ix-dropdown");
|
|
1752
|
+
if (!root) {
|
|
1753
|
+
this._close();
|
|
1754
|
+
this._trigger?.focus();
|
|
1755
|
+
return;
|
|
1756
|
+
}
|
|
1757
|
+
while (root.parentElement?.closest("ix-dropdown")) root = root.parentElement.closest("ix-dropdown");
|
|
1758
|
+
root._close();
|
|
1759
|
+
root._trigger?.focus();
|
|
1760
|
+
}
|
|
1761
|
+
_closeAllSubmenus() {
|
|
1762
|
+
(this._menu?.querySelectorAll("ix-dropdown[data-submenu]"))?.forEach((submenu) => {
|
|
1763
|
+
if (submenu._isOpen) submenu._close();
|
|
1764
|
+
});
|
|
1765
|
+
}
|
|
1766
|
+
_position() {
|
|
1767
|
+
if (!this._menu || !this._trigger) return;
|
|
1768
|
+
const triggerRect = this._trigger.getBoundingClientRect();
|
|
1769
|
+
const menuRect = this._menu.getBoundingClientRect();
|
|
1770
|
+
const vw = window.innerWidth;
|
|
1771
|
+
const vh = window.innerHeight;
|
|
1772
|
+
const gap = 2;
|
|
1773
|
+
let top;
|
|
1774
|
+
let left;
|
|
1775
|
+
if (this._isSubmenu) {
|
|
1776
|
+
const parentMenuRect = this.parentElement?.getBoundingClientRect();
|
|
1777
|
+
const anchorRight = parentMenuRect?.right ?? triggerRect.right;
|
|
1778
|
+
const anchorLeft = parentMenuRect?.left ?? triggerRect.left;
|
|
1779
|
+
const spaceRight = vw - anchorRight;
|
|
1780
|
+
const spaceLeft = anchorLeft;
|
|
1781
|
+
if (spaceRight >= menuRect.width || spaceRight >= spaceLeft) left = anchorRight + gap;
|
|
1782
|
+
else left = anchorLeft - menuRect.width - gap;
|
|
1783
|
+
top = triggerRect.top;
|
|
1784
|
+
if (top + menuRect.height > vh - gap) top = Math.max(gap, vh - menuRect.height - gap);
|
|
1785
|
+
} else {
|
|
1786
|
+
const [vertical, horizontal] = this._placement.split("-");
|
|
1787
|
+
const spaceBelow = vh - triggerRect.bottom;
|
|
1788
|
+
const spaceAbove = triggerRect.top;
|
|
1789
|
+
if (vertical === "bottom" && spaceBelow >= menuRect.height + gap) top = triggerRect.bottom + gap;
|
|
1790
|
+
else if (vertical === "top" && spaceAbove >= menuRect.height + gap) top = triggerRect.top - menuRect.height - gap;
|
|
1791
|
+
else if (spaceBelow >= spaceAbove) top = triggerRect.bottom + gap;
|
|
1792
|
+
else top = triggerRect.top - menuRect.height - gap;
|
|
1793
|
+
if (horizontal === "end") left = triggerRect.right - menuRect.width;
|
|
1794
|
+
else left = triggerRect.left;
|
|
1795
|
+
if (left + menuRect.width > vw - gap) left = vw - menuRect.width - gap;
|
|
1796
|
+
if (left < gap) left = gap;
|
|
1797
|
+
}
|
|
1798
|
+
if (top + menuRect.height > vh - gap) top = Math.max(gap, vh - menuRect.height - gap);
|
|
1799
|
+
if (top < gap) top = gap;
|
|
1800
|
+
this._menu.style.top = `${top}px`;
|
|
1801
|
+
this._menu.style.left = `${left}px`;
|
|
1802
|
+
}
|
|
1803
|
+
_addRepositionListeners() {
|
|
1804
|
+
if (this._onReposition) return;
|
|
1805
|
+
this._onReposition = () => {
|
|
1806
|
+
if (this._isOpen) this._position();
|
|
1807
|
+
};
|
|
1808
|
+
window.addEventListener("scroll", this._onReposition, true);
|
|
1809
|
+
window.addEventListener("resize", this._onReposition);
|
|
1810
|
+
}
|
|
1811
|
+
_removeRepositionListeners() {
|
|
1812
|
+
if (this._onReposition) {
|
|
1813
|
+
window.removeEventListener("scroll", this._onReposition, true);
|
|
1814
|
+
window.removeEventListener("resize", this._onReposition);
|
|
1815
|
+
this._onReposition = null;
|
|
1816
|
+
}
|
|
1817
|
+
}
|
|
1818
|
+
};
|
|
1819
|
+
//#endregion
|
|
1324
1820
|
//#region lib/components/field/IxField.ts
|
|
1325
1821
|
/**
|
|
1326
1822
|
* IxField — tilgjengelighetslim mellom label, input og hjelpetekster.
|
|
@@ -2493,9 +2989,10 @@ var IxPhoneNumberField = class extends HTMLElement {
|
|
|
2493
2989
|
const prop = flag === "readonly" ? "readOnly" : flag;
|
|
2494
2990
|
const read = () => mode === "attribute" ? control.hasAttribute(flag) : !!control[prop];
|
|
2495
2991
|
const write = (value) => {
|
|
2496
|
-
if (mode === "attribute")
|
|
2497
|
-
|
|
2498
|
-
|
|
2992
|
+
if (mode === "attribute") {
|
|
2993
|
+
if (value) control.setAttribute(flag, "");
|
|
2994
|
+
else control.removeAttribute(flag);
|
|
2995
|
+
} else control[prop] = value;
|
|
2499
2996
|
};
|
|
2500
2997
|
if (groupOn) {
|
|
2501
2998
|
if (!ownStore.has(control)) ownStore.set(control, read());
|
|
@@ -2597,6 +3094,317 @@ var IxPhoneNumberField = class extends HTMLElement {
|
|
|
2597
3094
|
}
|
|
2598
3095
|
};
|
|
2599
3096
|
//#endregion
|
|
3097
|
+
//#region lib/components/popover/IxPopover.ts
|
|
3098
|
+
/**
|
|
3099
|
+
* IxPopover — ARIA-lim for en popover med kontekstuell informasjon eller handlinger.
|
|
3100
|
+
*
|
|
3101
|
+
* ## Designfilosofi
|
|
3102
|
+
*
|
|
3103
|
+
* Komponenten kobler en trigger-knapp med et innholdspanel. Forfatteren skriver
|
|
3104
|
+
* en <button> (trigger) og en .ix-popover__content i light DOM. Komponentens jobb
|
|
3105
|
+
* er å sette opp ARIA-attributter, håndtere åpne/lukke-logikk, posisjonere panelet,
|
|
3106
|
+
* og styre fokus.
|
|
3107
|
+
*
|
|
3108
|
+
* ## Fokushåndtering
|
|
3109
|
+
*
|
|
3110
|
+
* Når popoveren åpnes, flyttes fokus til første focusable element i content (hvis det
|
|
3111
|
+
* finnes). Når popoveren lukkes med Escape, returneres fokus til trigger. Fokus trappes IKKE —
|
|
3112
|
+
* brukeren kan tabbe ut av popoveren naturlig.
|
|
3113
|
+
*
|
|
3114
|
+
* ## Posisjonering
|
|
3115
|
+
*
|
|
3116
|
+
* Panelet posisjoneres manuelt med getBoundingClientRect + viewport-clamp
|
|
3117
|
+
* (samme grep som dropdown og tooltip) — position: fixed, JS setter top/left.
|
|
3118
|
+
* Auto-flip brukes hvis det ikke er nok plass i ønsket retning.
|
|
3119
|
+
*
|
|
3120
|
+
* @example
|
|
3121
|
+
* <ix-popover placement="top">
|
|
3122
|
+
* <button class="ix-button">Åpne popover</button>
|
|
3123
|
+
* <div class="ix-popover__content">
|
|
3124
|
+
* <div class="ix-popover__heading">Overskrift</div>
|
|
3125
|
+
* <div class="ix-popover__body">Forklarende tekst her.</div>
|
|
3126
|
+
* <div class="ix-popover__actions">
|
|
3127
|
+
* <button class="ix-button" data-size="sm">OK</button>
|
|
3128
|
+
* </div>
|
|
3129
|
+
* </div>
|
|
3130
|
+
* </ix-popover>
|
|
3131
|
+
*/
|
|
3132
|
+
var popoverCounter = 0;
|
|
3133
|
+
var IxPopover = class extends HTMLElement {
|
|
3134
|
+
constructor(..._args) {
|
|
3135
|
+
super(..._args);
|
|
3136
|
+
_defineProperty(this, "_instanceId", 0);
|
|
3137
|
+
_defineProperty(this, "_trigger", null);
|
|
3138
|
+
_defineProperty(this, "_content", null);
|
|
3139
|
+
_defineProperty(this, "_onTriggerClick", null);
|
|
3140
|
+
_defineProperty(this, "_onTriggerKeydown", null);
|
|
3141
|
+
_defineProperty(this, "_onContentKeydown", null);
|
|
3142
|
+
_defineProperty(this, "_onDocPointer", null);
|
|
3143
|
+
_defineProperty(this, "_onFocusout", null);
|
|
3144
|
+
_defineProperty(this, "_onReposition", null);
|
|
3145
|
+
}
|
|
3146
|
+
static get observedAttributes() {
|
|
3147
|
+
return ["open", "placement"];
|
|
3148
|
+
}
|
|
3149
|
+
/**
|
|
3150
|
+
* Kontrollert modus: når `data-controlled` er satt, endrer ikke komponenten
|
|
3151
|
+
* egen åpen-tilstand. Den dispatcher kun `open-request` / `close-request`,
|
|
3152
|
+
* og foreldren må sette `open`-attributtet for å faktisk åpne/lukke.
|
|
3153
|
+
*/
|
|
3154
|
+
get _isControlled() {
|
|
3155
|
+
return this.hasAttribute("data-controlled");
|
|
3156
|
+
}
|
|
3157
|
+
connectedCallback() {
|
|
3158
|
+
this._instanceId = ++popoverCounter;
|
|
3159
|
+
this._wire();
|
|
3160
|
+
}
|
|
3161
|
+
disconnectedCallback() {
|
|
3162
|
+
if (this._trigger) {
|
|
3163
|
+
if (this._onTriggerClick) this._trigger.removeEventListener("click", this._onTriggerClick);
|
|
3164
|
+
if (this._onTriggerKeydown) this._trigger.removeEventListener("keydown", this._onTriggerKeydown);
|
|
3165
|
+
}
|
|
3166
|
+
if (this._content) {
|
|
3167
|
+
if (this._onContentKeydown) this._content.removeEventListener("keydown", this._onContentKeydown);
|
|
3168
|
+
}
|
|
3169
|
+
if (this._onDocPointer) document.removeEventListener("pointerdown", this._onDocPointer, true);
|
|
3170
|
+
if (this._onFocusout) this.removeEventListener("focusout", this._onFocusout);
|
|
3171
|
+
this._removeRepositionListeners();
|
|
3172
|
+
this._onTriggerClick = null;
|
|
3173
|
+
this._onTriggerKeydown = null;
|
|
3174
|
+
this._onContentKeydown = null;
|
|
3175
|
+
this._onDocPointer = null;
|
|
3176
|
+
this._onFocusout = null;
|
|
3177
|
+
this._onReposition = null;
|
|
3178
|
+
this._trigger = null;
|
|
3179
|
+
this._content = null;
|
|
3180
|
+
}
|
|
3181
|
+
attributeChangedCallback(name, _oldValue, newValue) {
|
|
3182
|
+
if (!this.isConnected) return;
|
|
3183
|
+
if (name === "open") {
|
|
3184
|
+
const shouldBeOpen = newValue !== null && newValue !== "false";
|
|
3185
|
+
if (shouldBeOpen && !this._isOpen) this._doOpen();
|
|
3186
|
+
else if (!shouldBeOpen && this._isOpen) this._doClose();
|
|
3187
|
+
} else if (name === "placement" && this._isOpen) this._position();
|
|
3188
|
+
}
|
|
3189
|
+
get open() {
|
|
3190
|
+
const attr = this.getAttribute("open");
|
|
3191
|
+
return attr !== null && attr !== "false";
|
|
3192
|
+
}
|
|
3193
|
+
set open(value) {
|
|
3194
|
+
this.toggleAttribute("open", value);
|
|
3195
|
+
}
|
|
3196
|
+
get _isOpen() {
|
|
3197
|
+
return this.hasAttribute("data-open");
|
|
3198
|
+
}
|
|
3199
|
+
get _placement() {
|
|
3200
|
+
return this.getAttribute("placement") ?? "top";
|
|
3201
|
+
}
|
|
3202
|
+
_wire() {
|
|
3203
|
+
this._content = this.querySelector(":scope > .ix-popover__content");
|
|
3204
|
+
this._trigger = Array.from(this.children).find((child) => child instanceof HTMLElement && !child.classList.contains("ix-popover__content")) ?? null;
|
|
3205
|
+
if (!this._trigger || !this._content) return;
|
|
3206
|
+
if (!this._content.id) this._content.id = `ix-popover-content-${this._instanceId}`;
|
|
3207
|
+
this._trigger.setAttribute("aria-haspopup", "dialog");
|
|
3208
|
+
this._trigger.setAttribute("aria-expanded", "false");
|
|
3209
|
+
this._trigger.setAttribute("aria-controls", this._content.id);
|
|
3210
|
+
this._content.hidden = true;
|
|
3211
|
+
this._content.setAttribute("role", "dialog");
|
|
3212
|
+
this._onTriggerClick = (e) => {
|
|
3213
|
+
e.preventDefault();
|
|
3214
|
+
e.stopPropagation();
|
|
3215
|
+
this._toggleOpen();
|
|
3216
|
+
};
|
|
3217
|
+
this._trigger.addEventListener("click", this._onTriggerClick);
|
|
3218
|
+
this._onTriggerKeydown = (e) => this._handleTriggerKeydown(e);
|
|
3219
|
+
this._trigger.addEventListener("keydown", this._onTriggerKeydown);
|
|
3220
|
+
this._onContentKeydown = (e) => this._handleContentKeydown(e);
|
|
3221
|
+
this._content.addEventListener("keydown", this._onContentKeydown);
|
|
3222
|
+
this._onDocPointer = (e) => {
|
|
3223
|
+
if (!this._isOpen) return;
|
|
3224
|
+
const target = e.target;
|
|
3225
|
+
if (!this.contains(target)) this._close();
|
|
3226
|
+
};
|
|
3227
|
+
document.addEventListener("pointerdown", this._onDocPointer, true);
|
|
3228
|
+
this._onFocusout = (e) => {
|
|
3229
|
+
if (!this._isOpen) return;
|
|
3230
|
+
const relatedTarget = e.relatedTarget;
|
|
3231
|
+
if (relatedTarget && this.contains(relatedTarget)) return;
|
|
3232
|
+
this._close();
|
|
3233
|
+
};
|
|
3234
|
+
this.addEventListener("focusout", this._onFocusout);
|
|
3235
|
+
if (this.open) this._doOpen();
|
|
3236
|
+
}
|
|
3237
|
+
_handleTriggerKeydown(e) {
|
|
3238
|
+
switch (e.key) {
|
|
3239
|
+
case "Enter":
|
|
3240
|
+
case " ":
|
|
3241
|
+
e.preventDefault();
|
|
3242
|
+
this._toggleOpen();
|
|
3243
|
+
break;
|
|
3244
|
+
case "ArrowDown": if (!this._isOpen) {
|
|
3245
|
+
e.preventDefault();
|
|
3246
|
+
this._open();
|
|
3247
|
+
}
|
|
3248
|
+
}
|
|
3249
|
+
}
|
|
3250
|
+
_handleContentKeydown(e) {
|
|
3251
|
+
if (e.key === "Escape") {
|
|
3252
|
+
e.preventDefault();
|
|
3253
|
+
e.stopPropagation();
|
|
3254
|
+
this._close();
|
|
3255
|
+
this._trigger?.focus();
|
|
3256
|
+
}
|
|
3257
|
+
}
|
|
3258
|
+
_open() {
|
|
3259
|
+
if (this._isOpen || !this._content || !this._trigger) return;
|
|
3260
|
+
if (this._isControlled) {
|
|
3261
|
+
this.dispatchEvent(new CustomEvent("open-request", { bubbles: true }));
|
|
3262
|
+
return;
|
|
3263
|
+
}
|
|
3264
|
+
this._doOpen();
|
|
3265
|
+
}
|
|
3266
|
+
_doOpen() {
|
|
3267
|
+
if (this._isOpen || !this._content || !this._trigger) return;
|
|
3268
|
+
this.setAttribute("data-open", "");
|
|
3269
|
+
this.setAttribute("open", "");
|
|
3270
|
+
this._content.hidden = false;
|
|
3271
|
+
this._trigger.setAttribute("aria-expanded", "true");
|
|
3272
|
+
this._position();
|
|
3273
|
+
this._addRepositionListeners();
|
|
3274
|
+
const firstFocusable = this._content.querySelector("button, [href], input, select, textarea, [tabindex]:not([tabindex=\"-1\"])");
|
|
3275
|
+
if (firstFocusable) firstFocusable.focus();
|
|
3276
|
+
this.dispatchEvent(new CustomEvent("open", { bubbles: true }));
|
|
3277
|
+
}
|
|
3278
|
+
_close() {
|
|
3279
|
+
if (!this._isOpen || !this._content || !this._trigger) return;
|
|
3280
|
+
if (this._isControlled) {
|
|
3281
|
+
this.dispatchEvent(new CustomEvent("close-request", { bubbles: true }));
|
|
3282
|
+
return;
|
|
3283
|
+
}
|
|
3284
|
+
this._doClose();
|
|
3285
|
+
}
|
|
3286
|
+
_doClose() {
|
|
3287
|
+
if (!this._isOpen || !this._content || !this._trigger) return;
|
|
3288
|
+
this.removeAttribute("data-open");
|
|
3289
|
+
this.removeAttribute("open");
|
|
3290
|
+
this._content.hidden = true;
|
|
3291
|
+
this._trigger.setAttribute("aria-expanded", "false");
|
|
3292
|
+
this._removeRepositionListeners();
|
|
3293
|
+
this.dispatchEvent(new CustomEvent("close", { bubbles: true }));
|
|
3294
|
+
}
|
|
3295
|
+
_toggleOpen() {
|
|
3296
|
+
if (this._isOpen) this._close();
|
|
3297
|
+
else this._open();
|
|
3298
|
+
}
|
|
3299
|
+
_position() {
|
|
3300
|
+
if (!this._content || !this._trigger) return;
|
|
3301
|
+
const triggerRect = this._trigger.getBoundingClientRect();
|
|
3302
|
+
const contentRect = this._content.getBoundingClientRect();
|
|
3303
|
+
const offsetRect = this._getFixedOffsetParent()?.getBoundingClientRect() ?? {
|
|
3304
|
+
top: 0,
|
|
3305
|
+
left: 0
|
|
3306
|
+
};
|
|
3307
|
+
const vw = window.innerWidth;
|
|
3308
|
+
const vh = window.innerHeight;
|
|
3309
|
+
const gap = 8;
|
|
3310
|
+
const arrowSize = 8;
|
|
3311
|
+
let top;
|
|
3312
|
+
let left;
|
|
3313
|
+
let actualPlacement = this._placement;
|
|
3314
|
+
const spaceAbove = triggerRect.top;
|
|
3315
|
+
const spaceBelow = vh - triggerRect.bottom;
|
|
3316
|
+
const spaceLeft = triggerRect.left;
|
|
3317
|
+
const spaceRight = vw - triggerRect.right;
|
|
3318
|
+
if (actualPlacement === "top" && spaceAbove < contentRect.height + gap + arrowSize) {
|
|
3319
|
+
if (spaceBelow >= contentRect.height + gap + arrowSize) actualPlacement = "bottom";
|
|
3320
|
+
} else if (actualPlacement === "bottom" && spaceBelow < contentRect.height + gap + arrowSize) {
|
|
3321
|
+
if (spaceAbove >= contentRect.height + gap + arrowSize) actualPlacement = "top";
|
|
3322
|
+
} else if (actualPlacement === "left" && spaceLeft < contentRect.width + gap + arrowSize) {
|
|
3323
|
+
if (spaceRight >= contentRect.width + gap + arrowSize) actualPlacement = "right";
|
|
3324
|
+
} else if (actualPlacement === "right" && spaceRight < contentRect.width + gap + arrowSize) {
|
|
3325
|
+
if (spaceLeft >= contentRect.width + gap + arrowSize) actualPlacement = "left";
|
|
3326
|
+
}
|
|
3327
|
+
const triggerCenterX = triggerRect.left + triggerRect.width / 2;
|
|
3328
|
+
const triggerCenterY = triggerRect.top + triggerRect.height / 2;
|
|
3329
|
+
switch (actualPlacement) {
|
|
3330
|
+
case "top":
|
|
3331
|
+
top = triggerRect.top - contentRect.height - gap - arrowSize;
|
|
3332
|
+
left = triggerCenterX - contentRect.width / 2;
|
|
3333
|
+
break;
|
|
3334
|
+
case "bottom":
|
|
3335
|
+
top = triggerRect.bottom + gap + arrowSize;
|
|
3336
|
+
left = triggerCenterX - contentRect.width / 2;
|
|
3337
|
+
break;
|
|
3338
|
+
case "left":
|
|
3339
|
+
top = triggerCenterY - contentRect.height / 2;
|
|
3340
|
+
left = triggerRect.left - contentRect.width - gap - arrowSize;
|
|
3341
|
+
break;
|
|
3342
|
+
case "right":
|
|
3343
|
+
top = triggerCenterY - contentRect.height / 2;
|
|
3344
|
+
left = triggerRect.right + gap + arrowSize;
|
|
3345
|
+
break;
|
|
3346
|
+
default:
|
|
3347
|
+
top = triggerRect.top - contentRect.height - gap - arrowSize;
|
|
3348
|
+
left = triggerCenterX - contentRect.width / 2;
|
|
3349
|
+
}
|
|
3350
|
+
if (left + contentRect.width > vw - gap) left = vw - contentRect.width - gap;
|
|
3351
|
+
if (left < gap) left = gap;
|
|
3352
|
+
if (top + contentRect.height > vh - gap) top = Math.max(gap, vh - contentRect.height - gap);
|
|
3353
|
+
if (top < gap) top = gap;
|
|
3354
|
+
top -= offsetRect.top;
|
|
3355
|
+
left -= offsetRect.left;
|
|
3356
|
+
this._content.style.top = `${top}px`;
|
|
3357
|
+
this._content.style.left = `${left}px`;
|
|
3358
|
+
this._content.setAttribute("data-placement", actualPlacement);
|
|
3359
|
+
const adjustedTriggerCenterX = triggerCenterX - offsetRect.left;
|
|
3360
|
+
const adjustedTriggerCenterY = triggerCenterY - offsetRect.top;
|
|
3361
|
+
let arrowX;
|
|
3362
|
+
let arrowY;
|
|
3363
|
+
if (actualPlacement === "top" || actualPlacement === "bottom") {
|
|
3364
|
+
arrowX = adjustedTriggerCenterX - left;
|
|
3365
|
+
arrowX = Math.max(16, Math.min(arrowX, contentRect.width - arrowSize - gap));
|
|
3366
|
+
this._content.style.setProperty("--_arrow-x", `${arrowX}px`);
|
|
3367
|
+
this._content.style.removeProperty("--_arrow-y");
|
|
3368
|
+
} else {
|
|
3369
|
+
arrowY = adjustedTriggerCenterY - top;
|
|
3370
|
+
arrowY = Math.max(16, Math.min(arrowY, contentRect.height - arrowSize - gap));
|
|
3371
|
+
this._content.style.setProperty("--_arrow-y", `${arrowY}px`);
|
|
3372
|
+
this._content.style.removeProperty("--_arrow-x");
|
|
3373
|
+
}
|
|
3374
|
+
}
|
|
3375
|
+
/**
|
|
3376
|
+
* Finner nærmeste ancestor med transform, filter, eller will-change som
|
|
3377
|
+
* skaper en ny containing block for position: fixed.
|
|
3378
|
+
*/
|
|
3379
|
+
_getFixedOffsetParent() {
|
|
3380
|
+
let el = this._content?.parentElement ?? null;
|
|
3381
|
+
while (el && el !== document.documentElement) {
|
|
3382
|
+
const style = getComputedStyle(el);
|
|
3383
|
+
const transform = style.transform;
|
|
3384
|
+
const willChange = style.willChange;
|
|
3385
|
+
const filter = style.filter;
|
|
3386
|
+
if (transform && transform !== "none" || willChange && (willChange.includes("transform") || willChange.includes("filter")) || filter && filter !== "none") return el;
|
|
3387
|
+
el = el.parentElement;
|
|
3388
|
+
}
|
|
3389
|
+
return null;
|
|
3390
|
+
}
|
|
3391
|
+
_addRepositionListeners() {
|
|
3392
|
+
if (this._onReposition) return;
|
|
3393
|
+
this._onReposition = () => {
|
|
3394
|
+
if (this._isOpen) this._position();
|
|
3395
|
+
};
|
|
3396
|
+
window.addEventListener("scroll", this._onReposition, true);
|
|
3397
|
+
window.addEventListener("resize", this._onReposition);
|
|
3398
|
+
}
|
|
3399
|
+
_removeRepositionListeners() {
|
|
3400
|
+
if (this._onReposition) {
|
|
3401
|
+
window.removeEventListener("scroll", this._onReposition, true);
|
|
3402
|
+
window.removeEventListener("resize", this._onReposition);
|
|
3403
|
+
this._onReposition = null;
|
|
3404
|
+
}
|
|
3405
|
+
}
|
|
3406
|
+
};
|
|
3407
|
+
//#endregion
|
|
2600
3408
|
//#region lib/utils/platform.ts
|
|
2601
3409
|
/**
|
|
2602
3410
|
* Plattform-deteksjon for web components.
|
|
@@ -3503,7 +4311,9 @@ customElements.define("ix-radio-group", IxRadioGroup);
|
|
|
3503
4311
|
customElements.define("ix-checkbox-group", IxCheckboxGroup);
|
|
3504
4312
|
customElements.define("ix-combobox", IxCombobox);
|
|
3505
4313
|
customElements.define("ix-date-field", IxDateField);
|
|
4314
|
+
customElements.define("ix-dropdown", IxDropdown);
|
|
3506
4315
|
customElements.define("ix-phone-number-field", IxPhoneNumberField);
|
|
4316
|
+
customElements.define("ix-popover", IxPopover);
|
|
3507
4317
|
customElements.define("ix-progress-bar", IxProgressBar);
|
|
3508
4318
|
customElements.define("ix-tabs", IxTabs);
|
|
3509
4319
|
customElements.define("ix-tab-list", IxTabList);
|
|
@@ -3511,4 +4321,4 @@ customElements.define("ix-tab", IxTab);
|
|
|
3511
4321
|
customElements.define("ix-tab-panel", IxTabPanel);
|
|
3512
4322
|
globalThis.cn = cn;
|
|
3513
4323
|
//#endregion
|
|
3514
|
-
export { BUILTIN_FORMAT_NAMES, COMMON_ICON_NAMES, IxCheckboxGroup, IxCombobox, IxDateField, IxField, IxIcon, IxPhoneNumberField, IxProgressBar, IxRadioGroup, IxTab, IxTabList, IxTabPanel, IxTabs, amountFormatterForLocale, cn, createAmountFormatter, createPatternFormatter, registerFormat, resolveFormat };
|
|
4324
|
+
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 };
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* IxDropdown — ARIA-lim for en nedtrekksmeny med handlinger eller navigasjon.
|
|
3
|
+
*
|
|
4
|
+
* ## Designfilosofi
|
|
5
|
+
*
|
|
6
|
+
* Komponenten kobler en trigger-knapp med en meny. Forfatteren skriver en
|
|
7
|
+
* <button> (trigger) og en .ix-dropdown__menu med .ix-dropdown__item-elementer
|
|
8
|
+
* i light DOM. Komponentens jobb er å sette opp ARIA-attributter, håndtere
|
|
9
|
+
* tastaturnavigasjon, posisjonere menyen, og styre åpen/lukket-tilstand.
|
|
10
|
+
*
|
|
11
|
+
* ## Roving tabindex
|
|
12
|
+
*
|
|
13
|
+
* Alle items har tabindex="-1" unntatt det aktive (fokuserte). Piltaster
|
|
14
|
+
* flytter fokus mellom items. Dette følger APG Menu Button-mønsteret.
|
|
15
|
+
*
|
|
16
|
+
* ## Submenyer
|
|
17
|
+
*
|
|
18
|
+
* En submenu er en nøstet <ix-dropdown data-submenu> inne i parent-menyen.
|
|
19
|
+
* ArrowRight på en submenu-trigger åpner submenyen, ArrowLeft lukker den.
|
|
20
|
+
* Submenyen posisjoneres til høyre for parent-item (flipper ved plassmangel).
|
|
21
|
+
*
|
|
22
|
+
* ## Posisjonering
|
|
23
|
+
*
|
|
24
|
+
* Menyen posisjoneres manuelt med getBoundingClientRect + viewport-clamp
|
|
25
|
+
* (samme grep som tooltip og combobox) — position: fixed, JS setter top/left.
|
|
26
|
+
*
|
|
27
|
+
* @example
|
|
28
|
+
* <ix-dropdown>
|
|
29
|
+
* <button class="ix-button" data-variant="secondary">Handlinger</button>
|
|
30
|
+
* <div class="ix-dropdown__menu">
|
|
31
|
+
* <button class="ix-dropdown__item">Rediger</button>
|
|
32
|
+
* <button class="ix-dropdown__item">Dupliser</button>
|
|
33
|
+
* <hr class="ix-dropdown__divider" />
|
|
34
|
+
* <button class="ix-dropdown__item" data-danger>Slett</button>
|
|
35
|
+
* </div>
|
|
36
|
+
* </ix-dropdown>
|
|
37
|
+
*/
|
|
38
|
+
export declare class IxDropdown extends HTMLElement {
|
|
39
|
+
private _instanceId;
|
|
40
|
+
private _trigger;
|
|
41
|
+
private _menu;
|
|
42
|
+
private _items;
|
|
43
|
+
private _activeIndex;
|
|
44
|
+
private _onTriggerClick;
|
|
45
|
+
private _onTriggerKeydown;
|
|
46
|
+
private _onMenuKeydown;
|
|
47
|
+
private _onMenuClick;
|
|
48
|
+
private _onMenuPointerenter;
|
|
49
|
+
private _onDocPointer;
|
|
50
|
+
private _onFocusout;
|
|
51
|
+
private _onReposition;
|
|
52
|
+
private _itemObserver;
|
|
53
|
+
private _typeaheadBuffer;
|
|
54
|
+
private _typeaheadTimer;
|
|
55
|
+
private _openedWithKeyboard;
|
|
56
|
+
static get observedAttributes(): string[];
|
|
57
|
+
/**
|
|
58
|
+
* Kontrollert modus: når `data-controlled` er satt, endrer ikke komponenten
|
|
59
|
+
* egen åpen-tilstand. Den dispatcher kun `open-request` / `close-request`,
|
|
60
|
+
* og foreldren må sette `open`-attributtet for å faktisk åpne/lukke.
|
|
61
|
+
*/
|
|
62
|
+
private get _isControlled();
|
|
63
|
+
connectedCallback(): void;
|
|
64
|
+
disconnectedCallback(): void;
|
|
65
|
+
attributeChangedCallback(name: string, _oldValue: string | null, newValue: string | null): void;
|
|
66
|
+
get open(): boolean;
|
|
67
|
+
set open(value: boolean);
|
|
68
|
+
private get _isOpen();
|
|
69
|
+
private get _isSubmenu();
|
|
70
|
+
private get _placement();
|
|
71
|
+
private _wire;
|
|
72
|
+
private _wireItems;
|
|
73
|
+
private _rewireItems;
|
|
74
|
+
private _handleTriggerKeydown;
|
|
75
|
+
private _handleMenuKeydown;
|
|
76
|
+
private _handleItemPointerEnter;
|
|
77
|
+
private _closeOtherSubmenus;
|
|
78
|
+
private _handleItemClick;
|
|
79
|
+
private _handleTypeahead;
|
|
80
|
+
private _focusItem;
|
|
81
|
+
private _focusNextItem;
|
|
82
|
+
private _open;
|
|
83
|
+
private _doOpen;
|
|
84
|
+
private _close;
|
|
85
|
+
private _doClose;
|
|
86
|
+
private _toggleOpen;
|
|
87
|
+
private _closeAll;
|
|
88
|
+
private _closeAllSubmenus;
|
|
89
|
+
private _position;
|
|
90
|
+
private _addRepositionListeners;
|
|
91
|
+
private _removeRepositionListeners;
|
|
92
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* IxPopover — ARIA-lim for en popover med kontekstuell informasjon eller handlinger.
|
|
3
|
+
*
|
|
4
|
+
* ## Designfilosofi
|
|
5
|
+
*
|
|
6
|
+
* Komponenten kobler en trigger-knapp med et innholdspanel. Forfatteren skriver
|
|
7
|
+
* en <button> (trigger) og en .ix-popover__content i light DOM. Komponentens jobb
|
|
8
|
+
* er å sette opp ARIA-attributter, håndtere åpne/lukke-logikk, posisjonere panelet,
|
|
9
|
+
* og styre fokus.
|
|
10
|
+
*
|
|
11
|
+
* ## Fokushåndtering
|
|
12
|
+
*
|
|
13
|
+
* Når popoveren åpnes, flyttes fokus til første focusable element i content (hvis det
|
|
14
|
+
* finnes). Når popoveren lukkes med Escape, returneres fokus til trigger. Fokus trappes IKKE —
|
|
15
|
+
* brukeren kan tabbe ut av popoveren naturlig.
|
|
16
|
+
*
|
|
17
|
+
* ## Posisjonering
|
|
18
|
+
*
|
|
19
|
+
* Panelet posisjoneres manuelt med getBoundingClientRect + viewport-clamp
|
|
20
|
+
* (samme grep som dropdown og tooltip) — position: fixed, JS setter top/left.
|
|
21
|
+
* Auto-flip brukes hvis det ikke er nok plass i ønsket retning.
|
|
22
|
+
*
|
|
23
|
+
* @example
|
|
24
|
+
* <ix-popover placement="top">
|
|
25
|
+
* <button class="ix-button">Åpne popover</button>
|
|
26
|
+
* <div class="ix-popover__content">
|
|
27
|
+
* <div class="ix-popover__heading">Overskrift</div>
|
|
28
|
+
* <div class="ix-popover__body">Forklarende tekst her.</div>
|
|
29
|
+
* <div class="ix-popover__actions">
|
|
30
|
+
* <button class="ix-button" data-size="sm">OK</button>
|
|
31
|
+
* </div>
|
|
32
|
+
* </div>
|
|
33
|
+
* </ix-popover>
|
|
34
|
+
*/
|
|
35
|
+
export declare class IxPopover extends HTMLElement {
|
|
36
|
+
private _instanceId;
|
|
37
|
+
private _trigger;
|
|
38
|
+
private _content;
|
|
39
|
+
private _onTriggerClick;
|
|
40
|
+
private _onTriggerKeydown;
|
|
41
|
+
private _onContentKeydown;
|
|
42
|
+
private _onDocPointer;
|
|
43
|
+
private _onFocusout;
|
|
44
|
+
private _onReposition;
|
|
45
|
+
static get observedAttributes(): string[];
|
|
46
|
+
/**
|
|
47
|
+
* Kontrollert modus: når `data-controlled` er satt, endrer ikke komponenten
|
|
48
|
+
* egen åpen-tilstand. Den dispatcher kun `open-request` / `close-request`,
|
|
49
|
+
* og foreldren må sette `open`-attributtet for å faktisk åpne/lukke.
|
|
50
|
+
*/
|
|
51
|
+
private get _isControlled();
|
|
52
|
+
connectedCallback(): void;
|
|
53
|
+
disconnectedCallback(): void;
|
|
54
|
+
attributeChangedCallback(name: string, _oldValue: string | null, newValue: string | null): void;
|
|
55
|
+
get open(): boolean;
|
|
56
|
+
set open(value: boolean);
|
|
57
|
+
private get _isOpen();
|
|
58
|
+
private get _placement();
|
|
59
|
+
private _wire;
|
|
60
|
+
private _handleTriggerKeydown;
|
|
61
|
+
private _handleContentKeydown;
|
|
62
|
+
private _open;
|
|
63
|
+
private _doOpen;
|
|
64
|
+
private _close;
|
|
65
|
+
private _doClose;
|
|
66
|
+
private _toggleOpen;
|
|
67
|
+
private _position;
|
|
68
|
+
/**
|
|
69
|
+
* Finner nærmeste ancestor med transform, filter, eller will-change som
|
|
70
|
+
* skaper en ny containing block for position: fixed.
|
|
71
|
+
*/
|
|
72
|
+
private _getFixedOffsetParent;
|
|
73
|
+
private _addRepositionListeners;
|
|
74
|
+
private _removeRepositionListeners;
|
|
75
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sb1/indeks-web",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.20.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Web components for Indeks design system",
|
|
6
6
|
"homepage": "https://design.sparebank1.no",
|
|
@@ -40,10 +40,10 @@
|
|
|
40
40
|
"author": "",
|
|
41
41
|
"license": "MIT",
|
|
42
42
|
"devDependencies": {
|
|
43
|
-
"@types/node": "^26.0
|
|
43
|
+
"@types/node": "^26.2.0",
|
|
44
44
|
"jsdom": "^29.1.1",
|
|
45
45
|
"typescript": "~6.0.3",
|
|
46
|
-
"vite": "^8.1
|
|
46
|
+
"vite": "^8.2.1",
|
|
47
47
|
"vite-plugin-dts": "^5.0.3",
|
|
48
48
|
"vitest": "^4.1.10"
|
|
49
49
|
},
|