fokus-styles 2.4.0 → 2.6.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/dist/css/components.css +561 -83
- package/dist/css/components.min.css +1 -1
- package/dist/css/fokus-components.css +561 -83
- package/dist/css/fokus-components.min.css +1 -1
- package/dist/css/fokus-rtl.css +697 -83
- package/dist/css/fokus-rtl.min.css +1 -1
- package/dist/css/fokus-utilities.css +136 -0
- package/dist/css/fokus-utilities.min.css +1 -1
- package/dist/css/fokus.css +996 -258
- package/dist/css/fokus.min.css +1 -1
- package/dist/css/forms.css +300 -176
- package/dist/css/forms.min.css +1 -1
- package/dist/css/helpers.css +136 -0
- package/dist/css/helpers.min.css +1 -1
- package/dist/js/fokus.js +270 -25
- package/dist/js/fokus.min.js +18 -18
- package/package.json +17 -15
- package/packages/fokus-components/scss/components/_alerts.scss +128 -6
- package/packages/fokus-components/scss/components/_badges.scss +26 -10
- package/packages/fokus-components/scss/components/_index.scss +1 -0
- package/packages/fokus-components/scss/components/_popover.scss +131 -21
- package/packages/fokus-components/scss/components/_responsive.scss +23 -0
- package/packages/fokus-components/scss/components/_tag.scss +37 -14
- package/packages/fokus-components/scss/components/_toasts.scss +156 -11
- package/packages/fokus-components/scss/components/_tooltips.scss +70 -18
- package/packages/fokus-components/scss/forms/_check-radio-switch.scss +313 -215
- package/packages/fokus-components/scss/forms/_forms.scss +16 -0
- package/packages/fokus-js/js/carousel.d.ts +4 -0
- package/packages/fokus-js/js/carousel.js +8 -0
- package/packages/fokus-js/js/core/positioning.js +18 -9
- package/packages/fokus-js/js/dropdown.d.ts +4 -0
- package/packages/fokus-js/js/dropdown.js +7 -0
- package/packages/fokus-js/js/popover.js +26 -1
- package/packages/fokus-js/js/tabs.d.ts +3 -0
- package/packages/fokus-js/js/tabs.js +5 -0
- package/packages/fokus-js/js/tag.js +17 -0
- package/packages/fokus-js/js/toast.js +77 -3
- package/packages/fokus-js/js/tooltip.d.ts +8 -1
- package/packages/fokus-js/js/tooltip.js +106 -13
- package/packages/fokus-utilities/scss/utilities/_api.scss +48 -0
- package/CHANGELOG.md +0 -910
- package/dist/css/components.css.map +0 -1
- package/dist/css/components.min.css.map +0 -1
- package/dist/css/fokus-components.css.map +0 -1
- package/dist/css/fokus-components.min.css.map +0 -1
- package/dist/css/fokus-core.css.map +0 -1
- package/dist/css/fokus-core.min.css.map +0 -1
- package/dist/css/fokus-dark.css.map +0 -1
- package/dist/css/fokus-dark.min.css.map +0 -1
- package/dist/css/fokus-rtl.css.map +0 -1
- package/dist/css/fokus-rtl.min.css.map +0 -1
- package/dist/css/fokus-utilities.css.map +0 -1
- package/dist/css/fokus-utilities.min.css.map +0 -1
- package/dist/css/fokus.css.map +0 -1
- package/dist/css/fokus.min.css.map +0 -1
- package/dist/css/fonts.css.map +0 -1
- package/dist/css/fonts.min.css.map +0 -1
- package/dist/css/forms.css.map +0 -1
- package/dist/css/forms.min.css.map +0 -1
- package/dist/css/helpers.css.map +0 -1
- package/dist/css/helpers.min.css.map +0 -1
- package/dist/css/layout.css.map +0 -1
- package/dist/css/layout.min.css.map +0 -1
- package/dist/js/fokus.js.map +0 -7
- package/dist/js/fokus.min.js.map +0 -7
- package/docs/README.md +0 -86
- package/docs/comparison.md +0 -124
- package/docs/components/accordion.md +0 -104
- package/docs/components/alert-dialog.md +0 -99
- package/docs/components/alert.md +0 -59
- package/docs/components/avatar.md +0 -13
- package/docs/components/badge.md +0 -82
- package/docs/components/breadcrumb.md +0 -93
- package/docs/components/button-group.md +0 -13
- package/docs/components/button.md +0 -98
- package/docs/components/card.md +0 -111
- package/docs/components/carousel.md +0 -167
- package/docs/components/checkbox.md +0 -97
- package/docs/components/close-button.md +0 -13
- package/docs/components/code.md +0 -13
- package/docs/components/collapse.md +0 -78
- package/docs/components/combobox.md +0 -123
- package/docs/components/command-palette.md +0 -131
- package/docs/components/datatable.md +0 -173
- package/docs/components/datepicker.md +0 -137
- package/docs/components/divider.md +0 -57
- package/docs/components/dropdown.md +0 -103
- package/docs/components/empty-state.md +0 -65
- package/docs/components/file-upload-advanced.md +0 -116
- package/docs/components/file-upload.md +0 -93
- package/docs/components/icon-link.md +0 -13
- package/docs/components/input-group.md +0 -73
- package/docs/components/input.md +0 -90
- package/docs/components/list-group.md +0 -13
- package/docs/components/modal.md +0 -120
- package/docs/components/navbar.md +0 -74
- package/docs/components/nested-menu.md +0 -90
- package/docs/components/notification-center.md +0 -116
- package/docs/components/offcanvas.md +0 -102
- package/docs/components/pagination.md +0 -145
- package/docs/components/placeholder.md +0 -13
- package/docs/components/popover.md +0 -105
- package/docs/components/progress.md +0 -126
- package/docs/components/radio.md +0 -70
- package/docs/components/range.md +0 -83
- package/docs/components/rating.md +0 -91
- package/docs/components/ratio.md +0 -13
- package/docs/components/scrollspy.md +0 -13
- package/docs/components/segmented-control.md +0 -82
- package/docs/components/select.md +0 -89
- package/docs/components/skeleton.md +0 -70
- package/docs/components/stepper.md +0 -107
- package/docs/components/switch.md +0 -75
- package/docs/components/table.md +0 -90
- package/docs/components/tabs.md +0 -104
- package/docs/components/tag.md +0 -129
- package/docs/components/tile.md +0 -112
- package/docs/components/timeline.md +0 -78
- package/docs/components/toast.md +0 -95
- package/docs/components/tooltip.md +0 -77
- package/docs/components/tree-view.md +0 -118
- package/docs/contributing/contributing.md +0 -18
- package/docs/getting-started/installation.md +0 -70
- package/docs/getting-started/usage.md +0 -105
- package/docs/guides/accessibility.md +0 -102
- package/docs/guides/charts.md +0 -73
- package/docs/guides/dark-mode.md +0 -90
- package/docs/guides/icons.md +0 -99
- package/docs/guides/javascript-api.md +0 -9
- package/docs/guides/layout-advanced.md +0 -118
- package/docs/guides/migration-clarus-to-fokus.md +0 -48
- package/docs/guides/migration-external.md +0 -123
- package/docs/guides/migration-v1.md +0 -63
- package/docs/guides/print.md +0 -30
- package/docs/guides/rtl-and-system-preferences.md +0 -28
- package/docs/guides/theming.md +0 -140
- package/docs/guides/utility-api.md +0 -9
- package/docs/prompts/prompt-plan.md +0 -45
- package/docs/reference/accessibility-matrix.md +0 -77
- package/docs/reference/browser-support.md +0 -75
- package/docs/reference/contrast-report.md +0 -50
- package/docs/reference/definitions.md +0 -864
- package/docs/reference/design-tokens.md +0 -194
- package/docs/reference/scss-architecture.md +0 -227
- package/docs/reference/size-baseline.json +0 -34
- package/docs/reference/stability.md +0 -89
- package/docs/showcase.md +0 -25
- package/mockup/README.md +0 -41
- package/mockup/assets/carousel-planning.png +0 -0
- package/mockup/assets/carousel-workspace.png +0 -0
- package/mockup/assets/example-theme.css +0 -8
- package/mockup/assets/example-theme.js +0 -11
- package/mockup/assets/showcase-contracts.js +0 -84
- package/mockup/assets/showcase.css +0 -91
- package/mockup/assets/showcase.js +0 -724
- package/mockup/content-data.html +0 -9
- package/mockup/examples/accordion-tabs-toast.html +0 -149
- package/mockup/examples/alert-dialog.html +0 -84
- package/mockup/examples/alerts.html +0 -41
- package/mockup/examples/badges-alerts.html +0 -77
- package/mockup/examples/buttons.html +0 -58
- package/mockup/examples/cards.html +0 -132
- package/mockup/examples/carousel.html +0 -126
- package/mockup/examples/charts.html +0 -102
- package/mockup/examples/check-radio-switch.html +0 -144
- package/mockup/examples/collapse.html +0 -69
- package/mockup/examples/combobox.html +0 -60
- package/mockup/examples/command-palette.html +0 -64
- package/mockup/examples/datatable.html +0 -113
- package/mockup/examples/datepicker.html +0 -62
- package/mockup/examples/divider.html +0 -45
- package/mockup/examples/dropdown-tooltip.html +0 -77
- package/mockup/examples/empty-state.html +0 -56
- package/mockup/examples/file-drop.html +0 -74
- package/mockup/examples/file-upload-advanced.html +0 -68
- package/mockup/examples/forms-advanced.html +0 -77
- package/mockup/examples/hover-card.html +0 -86
- package/mockup/examples/icons.html +0 -157
- package/mockup/examples/input-group.html +0 -74
- package/mockup/examples/js-foundation.html +0 -215
- package/mockup/examples/layout.html +0 -134
- package/mockup/examples/modal-select.html +0 -127
- package/mockup/examples/nested-menu.html +0 -73
- package/mockup/examples/notification-center.html +0 -86
- package/mockup/examples/offcanvas-popover.html +0 -133
- package/mockup/examples/pagination-breadcrumbs.html +0 -92
- package/mockup/examples/range.html +0 -69
- package/mockup/examples/rating.html +0 -103
- package/mockup/examples/segmented-control.html +0 -96
- package/mockup/examples/skeletons.html +0 -82
- package/mockup/examples/spinner-progress.html +0 -130
- package/mockup/examples/stepper.html +0 -119
- package/mockup/examples/tables-navbar.html +0 -89
- package/mockup/examples/tag.html +0 -105
- package/mockup/examples/theming.html +0 -68
- package/mockup/examples/tile.html +0 -107
- package/mockup/examples/timeline.html +0 -108
- package/mockup/examples/tree-view.html +0 -68
- package/mockup/feedback-actions.html +0 -9
- package/mockup/forms.html +0 -13
- package/mockup/foundations.html +0 -7
- package/mockup/kitchen-sink.html +0 -696
- package/mockup/navigation-disclosure.html +0 -11
- package/mockup/overlays-commands.html +0 -12
- package/mockup/templates/README.md +0 -20
- package/mockup/templates/admin.html +0 -334
- package/mockup/templates/auth.html +0 -234
- package/mockup/templates/dashboard.html +0 -442
- package/mockup/templates/landing.html +0 -397
- package/packages/fokus-icons/package.json +0 -46
- package/scripts/migrate-fokus-map.json +0 -1366
- package/scripts/migrate-fokus.mjs +0 -97
|
@@ -2,6 +2,22 @@
|
|
|
2
2
|
@use "../../../fokus-core/scss/tools" as *;
|
|
3
3
|
|
|
4
4
|
@layer components {
|
|
5
|
+
.fs-form-fieldset {
|
|
6
|
+
min-inline-size: 0;
|
|
7
|
+
margin: 0;
|
|
8
|
+
padding: var(--fs-space-3);
|
|
9
|
+
border: var(--fs-border-width) solid var(--fs-color-border);
|
|
10
|
+
border-radius: var(--fs-radius-md);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
.fs-form-fieldset > legend {
|
|
14
|
+
max-inline-size: 100%;
|
|
15
|
+
padding-inline: var(--fs-space-1);
|
|
16
|
+
color: var(--fs-color-text);
|
|
17
|
+
font-size: $font-size-sm;
|
|
18
|
+
font-weight: $font-weight-semibold;
|
|
19
|
+
}
|
|
20
|
+
|
|
5
21
|
.fs-form-row {
|
|
6
22
|
display: flex;
|
|
7
23
|
flex-direction: row;
|
|
@@ -4,6 +4,10 @@ export class Carousel {
|
|
|
4
4
|
constructor(carouselEl: HTMLElement);
|
|
5
5
|
|
|
6
6
|
static getInstance(el: Element): Carousel | undefined;
|
|
7
|
+
static getOrCreateInstance(el: Element): Carousel;
|
|
8
|
+
static next(el: Element): void;
|
|
9
|
+
static prev(el: Element): void;
|
|
10
|
+
static goTo(el: Element, index: number): void;
|
|
7
11
|
|
|
8
12
|
next(): void;
|
|
9
13
|
prev(): void;
|
|
@@ -91,6 +91,11 @@ export class Carousel {
|
|
|
91
91
|
return instances.get(el);
|
|
92
92
|
}
|
|
93
93
|
|
|
94
|
+
static getOrCreateInstance(el) { return this.getInstance(el) ?? new Carousel(el); }
|
|
95
|
+
static next(el) { return this.getOrCreateInstance(el).next(); }
|
|
96
|
+
static prev(el) { return this.getOrCreateInstance(el).prev(); }
|
|
97
|
+
static goTo(el, index) { return this.getOrCreateInstance(el).goTo(index); }
|
|
98
|
+
|
|
94
99
|
_isDocumentVisible() {
|
|
95
100
|
return document.visibilityState !== "hidden";
|
|
96
101
|
}
|
|
@@ -165,6 +170,9 @@ export class Carousel {
|
|
|
165
170
|
|
|
166
171
|
this.index = to;
|
|
167
172
|
this._render();
|
|
173
|
+
this.carouselEl.dispatchEvent(
|
|
174
|
+
new CustomEvent("fs:shown", { bubbles: true, detail: { from, to } }),
|
|
175
|
+
);
|
|
168
176
|
this.carouselEl.dispatchEvent(
|
|
169
177
|
new CustomEvent("fs:carousel:slid", { bubbles: true, detail: { from, to } }),
|
|
170
178
|
);
|
|
@@ -32,21 +32,18 @@ function alignCrossAxis(align, referenceStart, referenceSize, floatingSize) {
|
|
|
32
32
|
}
|
|
33
33
|
|
|
34
34
|
export function computePosition(referenceEl, floatingEl, options = {}) {
|
|
35
|
-
const { placement = "bottom", align = "center", offset = 8, padding = 8 } = options;
|
|
35
|
+
const { placement = "bottom", align = "center", offset = 8, padding = 8, fallbackPlacements } = options;
|
|
36
36
|
|
|
37
37
|
const referenceRect = referenceEl.getBoundingClientRect();
|
|
38
38
|
const floatingRect = floatingEl.getBoundingClientRect();
|
|
39
39
|
const viewportWidth = document.documentElement.clientWidth;
|
|
40
40
|
const viewportHeight = document.documentElement.clientHeight;
|
|
41
41
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
) {
|
|
48
|
-
finalPlacement = OPPOSITE_PLACEMENT[placement];
|
|
49
|
-
}
|
|
42
|
+
const candidates = [...new Set(fallbackPlacements ?? [placement, OPPOSITE_PLACEMENT[placement], "bottom", "top", "right", "left"])]
|
|
43
|
+
.filter(Boolean);
|
|
44
|
+
const primaryCandidates = candidates.slice(0, 2);
|
|
45
|
+
const finalPlacement = primaryCandidates.find((candidate) => fitsPlacement(candidate, referenceRect, floatingRect, offset, viewportWidth, viewportHeight))
|
|
46
|
+
?? placement;
|
|
50
47
|
|
|
51
48
|
let top;
|
|
52
49
|
let left;
|
|
@@ -70,10 +67,18 @@ export function computePosition(referenceEl, floatingEl, options = {}) {
|
|
|
70
67
|
left = Math.min(Math.max(left, padding), maxLeft);
|
|
71
68
|
top = Math.min(Math.max(top, padding), maxTop);
|
|
72
69
|
|
|
70
|
+
const crossSize = finalPlacement === "top" || finalPlacement === "bottom" ? floatingRect.width : floatingRect.height;
|
|
71
|
+
const crossStart = finalPlacement === "top" || finalPlacement === "bottom" ? left : top;
|
|
72
|
+
const referenceCenter = finalPlacement === "top" || finalPlacement === "bottom"
|
|
73
|
+
? referenceRect.left + referenceRect.width / 2
|
|
74
|
+
: referenceRect.top + referenceRect.height / 2;
|
|
75
|
+
const crossOffset = Math.min(Math.max(referenceCenter - crossStart, 8), Math.max(8, crossSize - 8));
|
|
76
|
+
|
|
73
77
|
return {
|
|
74
78
|
top: top + window.scrollY,
|
|
75
79
|
left: left + window.scrollX,
|
|
76
80
|
placement: finalPlacement,
|
|
81
|
+
arrowOffset: `${crossOffset}px`,
|
|
77
82
|
};
|
|
78
83
|
}
|
|
79
84
|
|
|
@@ -109,4 +114,8 @@ export function applyPosition(floatingEl, position) {
|
|
|
109
114
|
floatingEl.style.position = "absolute";
|
|
110
115
|
floatingEl.style.top = `${position.top}px`;
|
|
111
116
|
floatingEl.style.left = `${position.left}px`;
|
|
117
|
+
if (position.arrowOffset) {
|
|
118
|
+
floatingEl.style.setProperty("--fs-popover-arrow-offset", position.arrowOffset);
|
|
119
|
+
floatingEl.style.setProperty("--fs-tooltip-arrow-offset", position.arrowOffset);
|
|
120
|
+
}
|
|
112
121
|
}
|
|
@@ -13,6 +13,10 @@ export class Dropdown {
|
|
|
13
13
|
constructor(toggleEl: HTMLElement, options?: DropdownOptions);
|
|
14
14
|
|
|
15
15
|
static getInstance(el: Element): Dropdown | undefined;
|
|
16
|
+
static getOrCreateInstance(el: Element, options?: DropdownOptions): Dropdown;
|
|
17
|
+
static show(el: Element, options?: DropdownOptions): void;
|
|
18
|
+
static hide(el: Element): void;
|
|
19
|
+
static toggle(el: Element, options?: DropdownOptions): void;
|
|
16
20
|
|
|
17
21
|
show(): void;
|
|
18
22
|
hide(): void;
|
|
@@ -57,6 +57,11 @@ export class Dropdown {
|
|
|
57
57
|
return instances.get(el);
|
|
58
58
|
}
|
|
59
59
|
|
|
60
|
+
static getOrCreateInstance(el, options = {}) { return this.getInstance(el) ?? new Dropdown(el, options); }
|
|
61
|
+
static show(el, options = {}) { return this.getOrCreateInstance(el, options).show(); }
|
|
62
|
+
static hide(el) { return this.getInstance(el)?.hide(); }
|
|
63
|
+
static toggle(el, options = {}) { return this.getOrCreateInstance(el, options).toggle(); }
|
|
64
|
+
|
|
60
65
|
_handleToggleClick(event) {
|
|
61
66
|
event.preventDefault();
|
|
62
67
|
this.toggle();
|
|
@@ -127,6 +132,7 @@ export class Dropdown {
|
|
|
127
132
|
this.hide();
|
|
128
133
|
});
|
|
129
134
|
|
|
135
|
+
this.toggleEl.dispatchEvent(new CustomEvent("fs:shown", { bubbles: true }));
|
|
130
136
|
this.toggleEl.dispatchEvent(new CustomEvent("fs:dropdown:shown", { bubbles: true }));
|
|
131
137
|
}
|
|
132
138
|
|
|
@@ -145,6 +151,7 @@ export class Dropdown {
|
|
|
145
151
|
this._positionCleanup?.();
|
|
146
152
|
this._positionCleanup = null;
|
|
147
153
|
|
|
154
|
+
this.toggleEl.dispatchEvent(new CustomEvent("fs:hidden", { bubbles: true }));
|
|
148
155
|
this.toggleEl.dispatchEvent(new CustomEvent("fs:dropdown:hidden", { bubbles: true }));
|
|
149
156
|
}
|
|
150
157
|
|
|
@@ -6,6 +6,7 @@ import { autoInit, createInstanceRegistry } from "./core/register.js";
|
|
|
6
6
|
const instances = createInstanceRegistry();
|
|
7
7
|
let idCounter = 0;
|
|
8
8
|
const HOVER_HIDE_DELAY = 100;
|
|
9
|
+
const openPopovers = new Set();
|
|
9
10
|
|
|
10
11
|
export class Popover {
|
|
11
12
|
constructor(triggerEl, options = {}) {
|
|
@@ -25,6 +26,7 @@ export class Popover {
|
|
|
25
26
|
this._outsideClickCleanup = null;
|
|
26
27
|
this._removeEscapeListener = null;
|
|
27
28
|
this._hideTimer = null;
|
|
29
|
+
this._suppressNextFocus = false;
|
|
28
30
|
this._positionCleanup = null;
|
|
29
31
|
this._originalParent = popoverEl.parentNode;
|
|
30
32
|
this._originalNextSibling = popoverEl.nextSibling;
|
|
@@ -43,6 +45,12 @@ export class Popover {
|
|
|
43
45
|
popoverEl.setAttribute("aria-labelledby", headerEl.id);
|
|
44
46
|
}
|
|
45
47
|
|
|
48
|
+
const descriptionEl = popoverEl.querySelector(".fs-popover-text");
|
|
49
|
+
if (descriptionEl) {
|
|
50
|
+
if (!descriptionEl.id) descriptionEl.id = `fokus-popover-text-${idCounter}`;
|
|
51
|
+
popoverEl.setAttribute("aria-describedby", descriptionEl.id);
|
|
52
|
+
}
|
|
53
|
+
|
|
46
54
|
triggerEl.setAttribute("aria-controls", popoverEl.id);
|
|
47
55
|
triggerEl.setAttribute("aria-expanded", "false");
|
|
48
56
|
|
|
@@ -80,7 +88,13 @@ export class Popover {
|
|
|
80
88
|
this.popoverEl.addEventListener("mouseenter", this._handleMouseEnter);
|
|
81
89
|
this.popoverEl.addEventListener("mouseleave", this._handleMouseLeave);
|
|
82
90
|
} else if (this.trigger === "focus") {
|
|
83
|
-
this._handleTriggerFocus = () =>
|
|
91
|
+
this._handleTriggerFocus = () => {
|
|
92
|
+
if (this._suppressNextFocus) {
|
|
93
|
+
this._suppressNextFocus = false;
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
this.show();
|
|
97
|
+
};
|
|
84
98
|
this._handleTriggerFocusOut = (event) => {
|
|
85
99
|
if (this.popoverEl.contains(event.relatedTarget)) return;
|
|
86
100
|
this.hide();
|
|
@@ -105,7 +119,12 @@ export class Popover {
|
|
|
105
119
|
|
|
106
120
|
_handleDismissClick(event) {
|
|
107
121
|
if (event.target.closest('[data-fs-dismiss="popover"]')) {
|
|
122
|
+
const restoreFocus = this.popoverEl.contains(document.activeElement);
|
|
123
|
+
if (restoreFocus && this.trigger === "focus") this._suppressNextFocus = true;
|
|
108
124
|
this.hide();
|
|
125
|
+
if (restoreFocus && typeof this.triggerEl.focus === "function") {
|
|
126
|
+
this.triggerEl.focus();
|
|
127
|
+
}
|
|
109
128
|
}
|
|
110
129
|
}
|
|
111
130
|
|
|
@@ -122,6 +141,10 @@ export class Popover {
|
|
|
122
141
|
|
|
123
142
|
this.popoverEl.classList.add("is-open");
|
|
124
143
|
|
|
144
|
+
openPopovers.forEach((popover) => {
|
|
145
|
+
if (popover !== this) popover.hide();
|
|
146
|
+
});
|
|
147
|
+
|
|
125
148
|
const position = computePosition(this.triggerEl, this.popoverEl, {
|
|
126
149
|
placement: this.placement,
|
|
127
150
|
align: this.align,
|
|
@@ -134,6 +157,7 @@ export class Popover {
|
|
|
134
157
|
offset: 10,
|
|
135
158
|
});
|
|
136
159
|
this.popoverEl.setAttribute("data-placement", position.placement);
|
|
160
|
+
openPopovers.add(this);
|
|
137
161
|
|
|
138
162
|
this.triggerEl.setAttribute("aria-expanded", "true");
|
|
139
163
|
|
|
@@ -150,6 +174,7 @@ export class Popover {
|
|
|
150
174
|
hide() {
|
|
151
175
|
if (!this.isOpen) return;
|
|
152
176
|
this.isOpen = false;
|
|
177
|
+
openPopovers.delete(this);
|
|
153
178
|
|
|
154
179
|
this.popoverEl.classList.remove("is-open");
|
|
155
180
|
this._positionCleanup?.();
|
|
@@ -4,6 +4,9 @@ export class Tabs {
|
|
|
4
4
|
constructor(tablistEl: HTMLElement);
|
|
5
5
|
|
|
6
6
|
static getInstance(el: Element): Tabs | undefined;
|
|
7
|
+
static getOrCreateInstance(el: Element): Tabs;
|
|
8
|
+
static show(el: Element, tab: HTMLElement): void;
|
|
9
|
+
static dispose(el: Element): void;
|
|
7
10
|
|
|
8
11
|
show(tab: HTMLElement): void;
|
|
9
12
|
dispose(): void;
|
|
@@ -45,6 +45,10 @@ export class Tabs {
|
|
|
45
45
|
return instances.get(el);
|
|
46
46
|
}
|
|
47
47
|
|
|
48
|
+
static getOrCreateInstance(el) { return this.getInstance(el) ?? new Tabs(el); }
|
|
49
|
+
static show(el, tab) { return this.getOrCreateInstance(el).show(tab); }
|
|
50
|
+
static dispose(el) { return this.getInstance(el)?.dispose(); }
|
|
51
|
+
|
|
48
52
|
_handleClick(event) {
|
|
49
53
|
const tab = event.target.closest(".fs-nav-link:not(.is-disabled)");
|
|
50
54
|
if (!tab || !this.tabs.includes(tab)) return;
|
|
@@ -86,6 +90,7 @@ export class Tabs {
|
|
|
86
90
|
if (pane) pane.hidden = !isActive;
|
|
87
91
|
});
|
|
88
92
|
|
|
93
|
+
tab.dispatchEvent(new CustomEvent("fs:shown", { bubbles: true }));
|
|
89
94
|
tab.dispatchEvent(
|
|
90
95
|
new CustomEvent("fs:tab:changed", { bubbles: true, detail: { target: tab.getAttribute("data-fs-target") } }),
|
|
91
96
|
);
|
|
@@ -33,14 +33,31 @@ export class Tag {
|
|
|
33
33
|
dismiss() {
|
|
34
34
|
if (this._isLoading) return false;
|
|
35
35
|
|
|
36
|
+
const dismissButton = this.tagEl.querySelector('[data-fs-dismiss="tag"]');
|
|
37
|
+
const shouldRestoreFocus = dismissButton && document.activeElement === dismissButton;
|
|
36
38
|
const event = new CustomEvent("fs:tag:dismissed", { bubbles: true, cancelable: true });
|
|
37
39
|
this.tagEl.dispatchEvent(event);
|
|
38
40
|
if (event.defaultPrevented) return false;
|
|
39
41
|
|
|
42
|
+
const focusTarget = shouldRestoreFocus ? this._getFocusTarget() : null;
|
|
40
43
|
this.tagEl.remove();
|
|
44
|
+
focusTarget?.focus();
|
|
41
45
|
return true;
|
|
42
46
|
}
|
|
43
47
|
|
|
48
|
+
_getFocusTarget() {
|
|
49
|
+
const group = this.tagEl.closest(".fs-tag-group");
|
|
50
|
+
if (!group) return null;
|
|
51
|
+
|
|
52
|
+
const buttons = [...group.querySelectorAll('[data-fs-dismiss="tag"]')]
|
|
53
|
+
.filter((button) => !button.disabled && !button.hidden && button.getAttribute("aria-hidden") !== "true");
|
|
54
|
+
const currentButton = this.tagEl.querySelector('[data-fs-dismiss="tag"]');
|
|
55
|
+
const currentIndex = buttons.indexOf(currentButton);
|
|
56
|
+
const remaining = buttons.filter((button) => button !== currentButton);
|
|
57
|
+
|
|
58
|
+
return remaining[currentIndex] || remaining[currentIndex - 1] || null;
|
|
59
|
+
}
|
|
60
|
+
|
|
44
61
|
// Estado útil enquanto uma remoção ou atualização assíncrona está pendente.
|
|
45
62
|
// Além da aparência, bloqueia o dismiss e comunica o estado a AT.
|
|
46
63
|
setLoading(loading) {
|
|
@@ -10,13 +10,39 @@ export class Toast {
|
|
|
10
10
|
this.autohide = toastEl.getAttribute("data-autohide") !== "false";
|
|
11
11
|
this.isOpen = false;
|
|
12
12
|
this._hideTimer = null;
|
|
13
|
+
this._paused = false;
|
|
14
|
+
this._remaining = this.delay;
|
|
15
|
+
this._startedAt = 0;
|
|
16
|
+
this._progressTimer = null;
|
|
13
17
|
|
|
14
18
|
toastEl.setAttribute("role", "status");
|
|
15
19
|
toastEl.setAttribute("aria-live", "polite");
|
|
16
20
|
toastEl.style.display = "none";
|
|
17
21
|
|
|
18
22
|
this._handleDismissClick = this._handleDismissClick.bind(this);
|
|
23
|
+
this._pause = () => {
|
|
24
|
+
if (!this.isOpen || this._paused) return;
|
|
25
|
+
this._paused = true;
|
|
26
|
+
clearTimeout(this._hideTimer);
|
|
27
|
+
if (this.autohide && this._startedAt) {
|
|
28
|
+
this._remaining = Math.max(0, this._remaining - (Date.now() - this._startedAt));
|
|
29
|
+
}
|
|
30
|
+
this._updateProgress();
|
|
31
|
+
clearInterval(this._progressTimer);
|
|
32
|
+
this.toastEl.classList.add("is-paused");
|
|
33
|
+
};
|
|
34
|
+
this._resume = () => {
|
|
35
|
+
if (!this.isOpen || !this._paused) return;
|
|
36
|
+
this._paused = false;
|
|
37
|
+
this.toastEl.classList.remove("is-paused");
|
|
38
|
+
this._startProgress();
|
|
39
|
+
if (this.autohide) this._scheduleHide();
|
|
40
|
+
};
|
|
19
41
|
toastEl.addEventListener("click", this._handleDismissClick);
|
|
42
|
+
toastEl.addEventListener("mouseenter", this._pause);
|
|
43
|
+
toastEl.addEventListener("mouseleave", this._resume);
|
|
44
|
+
toastEl.addEventListener("focusin", this._pause);
|
|
45
|
+
toastEl.addEventListener("focusout", this._resume);
|
|
20
46
|
|
|
21
47
|
instances.set(toastEl, this);
|
|
22
48
|
}
|
|
@@ -35,17 +61,52 @@ export class Toast {
|
|
|
35
61
|
if (this.isOpen) return;
|
|
36
62
|
this.toastEl.dispatchEvent(new CustomEvent("fs:show", { bubbles: true }));
|
|
37
63
|
this.isOpen = true;
|
|
64
|
+
this._remaining = this.delay;
|
|
65
|
+
this._startedAt = Date.now();
|
|
66
|
+
this.toastEl.style.setProperty("--fs-toast-duration", `${this.delay}ms`);
|
|
67
|
+
|
|
68
|
+
const expansion = expand(this.toastEl);
|
|
69
|
+
this.toastEl.classList.remove("is-progressing");
|
|
70
|
+
void this.toastEl.offsetWidth;
|
|
71
|
+
if (this.toastEl.getAttribute("data-toast-progress") === "true" && this.autohide) {
|
|
72
|
+
this.toastEl.classList.add("is-progressing");
|
|
73
|
+
this._startProgress();
|
|
74
|
+
}
|
|
38
75
|
|
|
39
|
-
|
|
76
|
+
expansion.then(() => {
|
|
40
77
|
this.toastEl.dispatchEvent(new CustomEvent("fs:shown", { bubbles: true }));
|
|
41
78
|
this.toastEl.dispatchEvent(new CustomEvent("fs:toast:shown", { bubbles: true }));
|
|
42
79
|
});
|
|
43
80
|
|
|
44
|
-
|
|
45
|
-
|
|
81
|
+
this._scheduleHide();
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
_scheduleHide() {
|
|
85
|
+
clearTimeout(this._hideTimer);
|
|
86
|
+
if (this.autohide && !this._paused) {
|
|
87
|
+
this._startedAt = Date.now();
|
|
88
|
+
this._hideTimer = setTimeout(() => this.hide(), this._remaining);
|
|
46
89
|
}
|
|
47
90
|
}
|
|
48
91
|
|
|
92
|
+
_updateProgress() {
|
|
93
|
+
if (this.toastEl.getAttribute("data-toast-progress") !== "true" || !this.autohide) return;
|
|
94
|
+
const percentage = Math.max(0, Math.min(100, (this._remaining / this.delay) * 100));
|
|
95
|
+
const progressEl = this.toastEl.querySelector(".fs-toast-progress");
|
|
96
|
+
if (progressEl) progressEl.style.inlineSize = `${percentage}%`;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
_startProgress() {
|
|
100
|
+
if (this.toastEl.getAttribute("data-toast-progress") !== "true" || !this.autohide) return;
|
|
101
|
+
clearInterval(this._progressTimer);
|
|
102
|
+
this._updateProgress();
|
|
103
|
+
this._progressTimer = setInterval(() => {
|
|
104
|
+
if (!this.isOpen || this._paused) return;
|
|
105
|
+
this._remaining = Math.max(0, this.delay - (Date.now() - this._startedAt));
|
|
106
|
+
this._updateProgress();
|
|
107
|
+
}, 50);
|
|
108
|
+
}
|
|
109
|
+
|
|
49
110
|
hide() {
|
|
50
111
|
if (!this.isOpen) return;
|
|
51
112
|
this.toastEl.dispatchEvent(new CustomEvent("fs:hide", { bubbles: true }));
|
|
@@ -53,6 +114,14 @@ export class Toast {
|
|
|
53
114
|
|
|
54
115
|
clearTimeout(this._hideTimer);
|
|
55
116
|
this._hideTimer = null;
|
|
117
|
+
clearInterval(this._progressTimer);
|
|
118
|
+
this._progressTimer = null;
|
|
119
|
+
this._startedAt = 0;
|
|
120
|
+
this._remaining = this.delay;
|
|
121
|
+
this.toastEl.classList.remove("is-paused");
|
|
122
|
+
this.toastEl.classList.remove("is-progressing");
|
|
123
|
+
const progressEl = this.toastEl.querySelector(".fs-toast-progress");
|
|
124
|
+
if (progressEl) progressEl.style.removeProperty("inline-size");
|
|
56
125
|
|
|
57
126
|
collapse(this.toastEl).then(() => {
|
|
58
127
|
this.toastEl.dispatchEvent(new CustomEvent("fs:hidden", { bubbles: true }));
|
|
@@ -70,7 +139,12 @@ export class Toast {
|
|
|
70
139
|
|
|
71
140
|
dispose() {
|
|
72
141
|
clearTimeout(this._hideTimer);
|
|
142
|
+
clearInterval(this._progressTimer);
|
|
73
143
|
this.toastEl.removeEventListener("click", this._handleDismissClick);
|
|
144
|
+
this.toastEl.removeEventListener("mouseenter", this._pause);
|
|
145
|
+
this.toastEl.removeEventListener("mouseleave", this._resume);
|
|
146
|
+
this.toastEl.removeEventListener("focusin", this._pause);
|
|
147
|
+
this.toastEl.removeEventListener("focusout", this._resume);
|
|
74
148
|
instances.delete(this.toastEl);
|
|
75
149
|
}
|
|
76
150
|
}
|
|
@@ -1,8 +1,15 @@
|
|
|
1
1
|
import type { Placement } from "./core/positioning.js";
|
|
2
2
|
|
|
3
|
+
export type TooltipAlign = "start" | "center" | "end";
|
|
4
|
+
export type TooltipPlacement = Placement | `${Placement}-${Exclude<TooltipAlign, "center">}`;
|
|
5
|
+
|
|
3
6
|
export interface TooltipOptions {
|
|
4
|
-
placement?:
|
|
7
|
+
placement?: TooltipPlacement;
|
|
8
|
+
align?: TooltipAlign;
|
|
9
|
+
offset?: number;
|
|
5
10
|
title?: string;
|
|
11
|
+
showDelay?: number;
|
|
12
|
+
hideDelay?: number;
|
|
6
13
|
}
|
|
7
14
|
|
|
8
15
|
export class Tooltip {
|
|
@@ -4,6 +4,19 @@ import { autoInit, createInstanceRegistry } from "./core/register.js";
|
|
|
4
4
|
|
|
5
5
|
const instances = createInstanceRegistry();
|
|
6
6
|
let idCounter = 0;
|
|
7
|
+
let openInstance = null;
|
|
8
|
+
const DEFAULT_TOOLTIP_OFFSET = 8;
|
|
9
|
+
|
|
10
|
+
function toDelay(value, fallback = 0) {
|
|
11
|
+
if (value === null || value === undefined || value === "") return fallback;
|
|
12
|
+
const parsed = Number(value);
|
|
13
|
+
return Number.isFinite(parsed) && parsed >= 0 ? parsed : fallback;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function parsePlacement(value = "top") {
|
|
17
|
+
const [placement, align = "center"] = value.split("-");
|
|
18
|
+
return { placement, align: ["start", "center", "end"].includes(align) ? align : "center" };
|
|
19
|
+
}
|
|
7
20
|
|
|
8
21
|
export class Tooltip {
|
|
9
22
|
constructor(referenceEl, options = {}) {
|
|
@@ -19,10 +32,18 @@ export class Tooltip {
|
|
|
19
32
|
|
|
20
33
|
this.referenceEl = referenceEl;
|
|
21
34
|
this.title = title;
|
|
22
|
-
|
|
35
|
+
const parsedPlacement = parsePlacement(options.placement ?? referenceEl.getAttribute("data-placement") ?? "top");
|
|
36
|
+
this.placement = parsedPlacement.placement;
|
|
37
|
+
this.align = options.align ?? referenceEl.getAttribute("data-align") ?? parsedPlacement.align;
|
|
38
|
+
this.offset = toDelay(options.offset ?? referenceEl.getAttribute("data-offset"), DEFAULT_TOOLTIP_OFFSET);
|
|
39
|
+
this.showDelay = toDelay(options.showDelay ?? referenceEl.getAttribute("data-show-delay"), 0);
|
|
40
|
+
this.hideDelay = toDelay(options.hideDelay ?? referenceEl.getAttribute("data-hide-delay"));
|
|
23
41
|
this.isOpen = false;
|
|
24
42
|
this.id = `fokus-tooltip-${idCounter}`;
|
|
25
43
|
this._positionCleanup = null;
|
|
44
|
+
this._showTimer = null;
|
|
45
|
+
this._hideTimer = null;
|
|
46
|
+
this._touchOpened = false;
|
|
26
47
|
this._originalDescribedBy = referenceEl.getAttribute("aria-describedby");
|
|
27
48
|
|
|
28
49
|
this.tooltipEl = document.createElement("div");
|
|
@@ -33,16 +54,36 @@ export class Tooltip {
|
|
|
33
54
|
this.tooltipEl.querySelector(".fs-tooltip-inner").textContent = this.title;
|
|
34
55
|
document.body.appendChild(this.tooltipEl);
|
|
35
56
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
this._handleShow = this.
|
|
39
|
-
this._handleHide = this.
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
57
|
+
this._addDescribedBy();
|
|
58
|
+
|
|
59
|
+
this._handleShow = () => this._scheduleShow(true);
|
|
60
|
+
this._handleHide = () => this._scheduleHide();
|
|
61
|
+
|
|
62
|
+
this._handlePointerEnter = () => this._scheduleShow();
|
|
63
|
+
this._handlePointerLeave = () => this._scheduleHide();
|
|
64
|
+
this._handlePointerDown = (event) => {
|
|
65
|
+
if (event.pointerType === "touch") {
|
|
66
|
+
if (this._touchOpened && this.isOpen) {
|
|
67
|
+
this.hideImmediate();
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
this._touchOpened = true;
|
|
71
|
+
this._scheduleShow(true);
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
referenceEl.addEventListener("pointerenter", this._handlePointerEnter);
|
|
75
|
+
referenceEl.addEventListener("pointerleave", this._handlePointerLeave);
|
|
76
|
+
referenceEl.addEventListener("pointerdown", this._handlePointerDown);
|
|
77
|
+
referenceEl.addEventListener("mouseenter", this._handlePointerEnter);
|
|
78
|
+
referenceEl.addEventListener("mouseleave", this._handlePointerLeave);
|
|
43
79
|
referenceEl.addEventListener("focus", this._handleShow);
|
|
44
80
|
referenceEl.addEventListener("blur", this._handleHide);
|
|
45
81
|
|
|
82
|
+
this._handleDocumentPointerDown = (event) => {
|
|
83
|
+
if (this._touchOpened && !this.referenceEl.contains(event.target) && !this.tooltipEl.contains(event.target)) this.hide();
|
|
84
|
+
};
|
|
85
|
+
document.addEventListener("pointerdown", this._handleDocumentPointerDown, true);
|
|
86
|
+
|
|
46
87
|
this._removeEscapeListener = onEscapeKey(() => {
|
|
47
88
|
if (this.isOpen) this.hide();
|
|
48
89
|
});
|
|
@@ -54,8 +95,46 @@ export class Tooltip {
|
|
|
54
95
|
return instances.get(el);
|
|
55
96
|
}
|
|
56
97
|
|
|
98
|
+
_clearTimers() {
|
|
99
|
+
clearTimeout(this._showTimer);
|
|
100
|
+
clearTimeout(this._hideTimer);
|
|
101
|
+
this._showTimer = null;
|
|
102
|
+
this._hideTimer = null;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
_addDescribedBy() {
|
|
106
|
+
const ids = new Set((this.referenceEl.getAttribute("aria-describedby") ?? "").split(/\s+/).filter(Boolean));
|
|
107
|
+
ids.add(this.id);
|
|
108
|
+
this.referenceEl.setAttribute("aria-describedby", [...ids].join(" "));
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
_scheduleShow(immediate = false) {
|
|
112
|
+
this._clearTimers();
|
|
113
|
+
if (this.isOpen) return;
|
|
114
|
+
const delay = immediate ? 0 : this.showDelay;
|
|
115
|
+
if (delay === 0) {
|
|
116
|
+
this.show();
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
this._showTimer = setTimeout(() => this.show(), delay);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
_scheduleHide(immediate = false) {
|
|
123
|
+
this._clearTimers();
|
|
124
|
+
if (!this.isOpen) return;
|
|
125
|
+
const delay = immediate ? 0 : this.hideDelay;
|
|
126
|
+
if (delay === 0) {
|
|
127
|
+
this.hide();
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
this._hideTimer = setTimeout(() => this.hide(), delay);
|
|
131
|
+
}
|
|
132
|
+
|
|
57
133
|
show() {
|
|
134
|
+
this._clearTimers();
|
|
58
135
|
if (this.isOpen) return;
|
|
136
|
+
if (openInstance && openInstance !== this) openInstance.hide(true);
|
|
137
|
+
openInstance = this;
|
|
59
138
|
this.isOpen = true;
|
|
60
139
|
|
|
61
140
|
const theme = this.referenceEl.closest("[data-theme]")?.getAttribute("data-theme");
|
|
@@ -67,26 +146,35 @@ export class Tooltip {
|
|
|
67
146
|
|
|
68
147
|
this.tooltipEl.classList.add("is-open");
|
|
69
148
|
|
|
70
|
-
const position = computePosition(this.referenceEl, this.tooltipEl, { placement: this.placement, offset:
|
|
149
|
+
const position = computePosition(this.referenceEl, this.tooltipEl, { placement: this.placement, align: this.align, offset: this.offset });
|
|
71
150
|
applyPosition(this.tooltipEl, position);
|
|
72
151
|
this._positionCleanup = watchPosition(this.referenceEl, this.tooltipEl, {
|
|
73
152
|
placement: this.placement,
|
|
74
|
-
|
|
153
|
+
align: this.align,
|
|
154
|
+
offset: this.offset,
|
|
75
155
|
});
|
|
76
156
|
this.tooltipEl.setAttribute("data-placement", position.placement);
|
|
157
|
+
this.tooltipEl.setAttribute("data-align", this.align);
|
|
77
158
|
|
|
78
159
|
this.referenceEl.dispatchEvent(new CustomEvent("fs:tooltip:shown", { bubbles: true }));
|
|
79
160
|
}
|
|
80
161
|
|
|
81
162
|
hide() {
|
|
163
|
+
this.hideImmediate();
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
hideImmediate() {
|
|
167
|
+
this._clearTimers();
|
|
82
168
|
if (!this.isOpen) return;
|
|
83
169
|
this.isOpen = false;
|
|
170
|
+
this._touchOpened = false;
|
|
84
171
|
|
|
85
172
|
this.tooltipEl.classList.remove("is-open");
|
|
86
173
|
this._positionCleanup?.();
|
|
87
174
|
this._positionCleanup = null;
|
|
88
175
|
|
|
89
176
|
this.referenceEl.dispatchEvent(new CustomEvent("fs:tooltip:hidden", { bubbles: true }));
|
|
177
|
+
if (openInstance === this) openInstance = null;
|
|
90
178
|
}
|
|
91
179
|
|
|
92
180
|
toggle() {
|
|
@@ -98,11 +186,16 @@ export class Tooltip {
|
|
|
98
186
|
}
|
|
99
187
|
|
|
100
188
|
dispose() {
|
|
101
|
-
this.
|
|
102
|
-
this.
|
|
103
|
-
this.referenceEl.removeEventListener("
|
|
189
|
+
this.hideImmediate();
|
|
190
|
+
this._clearTimers();
|
|
191
|
+
this.referenceEl.removeEventListener("pointerenter", this._handlePointerEnter);
|
|
192
|
+
this.referenceEl.removeEventListener("pointerleave", this._handlePointerLeave);
|
|
193
|
+
this.referenceEl.removeEventListener("pointerdown", this._handlePointerDown);
|
|
194
|
+
this.referenceEl.removeEventListener("mouseenter", this._handlePointerEnter);
|
|
195
|
+
this.referenceEl.removeEventListener("mouseleave", this._handlePointerLeave);
|
|
104
196
|
this.referenceEl.removeEventListener("focus", this._handleShow);
|
|
105
197
|
this.referenceEl.removeEventListener("blur", this._handleHide);
|
|
198
|
+
document.removeEventListener("pointerdown", this._handleDocumentPointerDown, true);
|
|
106
199
|
this._removeEscapeListener();
|
|
107
200
|
if (this._originalDescribedBy === null) this.referenceEl.removeAttribute("aria-describedby");
|
|
108
201
|
else this.referenceEl.setAttribute("aria-describedby", this._originalDescribedBy);
|