fokus-styles 2.0.0 → 2.3.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 +13 -0
- package/README.md +4 -3
- package/dist/css/components.css +510 -2
- package/dist/css/components.css.map +1 -1
- package/dist/css/components.min.css +1 -1
- package/dist/css/components.min.css.map +1 -1
- package/dist/css/fokus-components.css +4050 -0
- package/dist/css/fokus-components.css.map +1 -0
- package/dist/css/fokus-components.min.css +2 -0
- package/dist/css/fokus-components.min.css.map +1 -0
- package/dist/css/fokus-core.css +1563 -0
- package/dist/css/fokus-core.css.map +1 -0
- package/dist/css/fokus-core.min.css +2 -0
- package/dist/css/fokus-core.min.css.map +1 -0
- package/dist/css/fokus-dark.css +304 -0
- package/dist/css/fokus-dark.css.map +1 -0
- package/dist/css/fokus-dark.min.css +2 -0
- package/dist/css/fokus-dark.min.css.map +1 -0
- package/dist/css/fokus-utilities.css +4176 -0
- package/dist/css/fokus-utilities.css.map +1 -0
- package/dist/css/fokus-utilities.min.css +2 -0
- package/dist/css/fokus-utilities.min.css.map +1 -0
- package/dist/css/fokus.css +1339 -53
- package/dist/css/fokus.css.map +1 -1
- package/dist/css/fokus.min.css +1 -1
- package/dist/css/fokus.min.css.map +1 -1
- package/dist/css/forms.css +115 -2
- package/dist/css/forms.css.map +1 -1
- package/dist/css/forms.min.css +1 -1
- package/dist/css/forms.min.css.map +1 -1
- package/dist/css/helpers.css +376 -2
- package/dist/css/helpers.css.map +1 -1
- package/dist/css/helpers.min.css +1 -1
- package/dist/css/helpers.min.css.map +1 -1
- package/dist/css/layout.css +560 -2
- package/dist/css/layout.css.map +1 -1
- package/dist/css/layout.min.css +1 -1
- package/dist/css/layout.min.css.map +1 -1
- package/dist/js/fokus.js +347 -12
- package/dist/js/fokus.js.map +4 -4
- package/dist/js/fokus.min.js +17 -17
- package/dist/js/fokus.min.js.map +4 -4
- package/docs/components/accordion.md +3 -0
- package/docs/components/avatar.md +13 -0
- package/docs/components/button-group.md +13 -0
- package/docs/components/close-button.md +13 -0
- package/docs/components/code.md +13 -0
- package/docs/components/icon-link.md +13 -0
- package/docs/components/list-group.md +13 -0
- package/docs/components/modal.md +3 -0
- package/docs/components/navbar.md +3 -0
- package/docs/components/pagination.md +3 -0
- package/docs/components/placeholder.md +13 -0
- package/docs/components/ratio.md +13 -0
- package/docs/components/scrollspy.md +13 -0
- package/docs/components/skeleton.md +3 -0
- package/docs/components/table.md +3 -0
- package/docs/guides/javascript-api.md +9 -0
- package/docs/guides/utility-api.md +9 -0
- package/docs/reference/size-baseline.json +13 -13
- package/mockup/assets/example-theme.css +3 -1
- package/mockup/assets/example-theme.js +2 -0
- package/mockup/assets/showcase-contracts.js +2 -2
- package/mockup/assets/showcase.css +4 -2
- package/mockup/assets/showcase.js +2 -0
- package/mockup/examples/js-foundation.html +25 -0
- package/mockup/examples/tables-navbar.html +12 -8
- package/mockup/examples/theming.html +2 -1
- package/mockup/foundations.html +2 -2
- package/package.json +14 -1
- package/packages/fokus-cli/bin/fokus.mjs +18 -1
- package/packages/fokus-cli/lib/build.mjs +12 -2
- package/packages/fokus-components/scss/components/_accordion.scss +19 -0
- package/packages/fokus-components/scss/components/_avatar.scss +22 -0
- package/packages/fokus-components/scss/components/_button-group.scss +14 -0
- package/packages/fokus-components/scss/components/_close-button.scss +22 -0
- package/packages/fokus-components/scss/components/_code.scss +19 -0
- package/packages/fokus-components/scss/components/_icon-link.scss +8 -0
- package/packages/fokus-components/scss/components/_index.scss +10 -0
- package/packages/fokus-components/scss/components/_list-group.scss +26 -0
- package/packages/fokus-components/scss/components/_modal.scss +36 -0
- package/packages/fokus-components/scss/components/_navbar.scss +48 -0
- package/packages/fokus-components/scss/components/_offcanvas.scss +19 -0
- package/packages/fokus-components/scss/components/_pagination.scss +10 -0
- package/packages/fokus-components/scss/components/_placeholder.scss +13 -0
- package/packages/fokus-components/scss/components/_progress.scss +8 -0
- package/packages/fokus-components/scss/components/_ratio.scss +10 -0
- package/packages/fokus-components/scss/components/_scrollspy.scss +5 -0
- package/packages/fokus-components/scss/components/_skeleton.scss +19 -0
- package/packages/fokus-components/scss/components/_spinner.scss +9 -53
- package/packages/fokus-components/scss/components/_tables.scss +20 -0
- package/packages/fokus-components/scss/forms/_forms.scss +29 -0
- package/packages/fokus-core/scss/base/_accessibility.scss +45 -0
- package/packages/fokus-core/scss/base/_index.scss +1 -0
- package/packages/fokus-core/scss/layout/_grid.scss +18 -0
- package/packages/fokus-core/scss/settings/_config.scss +43 -0
- package/packages/fokus-core/scss/settings/_index.scss +1 -0
- package/packages/fokus-core/scss/themes/_dark.scss +38 -2
- package/packages/fokus-core/scss/tokens/_root.scss +14 -0
- package/packages/fokus-core/scss/tools/_mixins.scss +8 -0
- package/packages/fokus-js/js/accordion.js +7 -1
- package/packages/fokus-js/js/core/focus.js +20 -0
- package/packages/fokus-js/js/core/overlay.d.ts +2 -0
- package/packages/fokus-js/js/core/overlay.js +10 -0
- package/packages/fokus-js/js/core/register.js +4 -2
- package/packages/fokus-js/js/focus-trap.d.ts +3 -0
- package/packages/fokus-js/js/focus-trap.js +1 -0
- package/packages/fokus-js/js/fokus.d.ts +7 -0
- package/packages/fokus-js/js/fokus.js +7 -0
- package/packages/fokus-js/js/form-validation.d.ts +7 -0
- package/packages/fokus-js/js/form-validation.js +20 -0
- package/packages/fokus-js/js/modal.js +16 -2
- package/packages/fokus-js/js/navbar.d.ts +10 -0
- package/packages/fokus-js/js/navbar.js +27 -0
- package/packages/fokus-js/js/offcanvas.d.ts +4 -0
- package/packages/fokus-js/js/offcanvas.js +16 -2
- package/packages/fokus-js/js/pagination.d.ts +7 -0
- package/packages/fokus-js/js/pagination.js +38 -0
- package/packages/fokus-js/js/scrollspy.d.ts +7 -0
- package/packages/fokus-js/js/scrollspy.js +26 -0
- package/packages/fokus-js/js/spinner.d.ts +9 -0
- package/packages/fokus-js/js/spinner.js +18 -0
- package/packages/fokus-js/js/theme.d.ts +9 -0
- package/packages/fokus-js/js/theme.js +19 -0
- package/packages/fokus-js/js/toast.js +4 -0
- package/packages/fokus-utilities/scss/utilities/_api.scss +48 -0
- package/packages/fokus-utilities/scss/utilities/_index.scss +2 -0
- package/packages/fokus-utilities/scss/utilities/_states.scss +19 -0
- package/packages/fokus-utilities/scss/utilities/_visibility.scss +24 -0
- package/scss/entries/core-entry.scss +5 -0
- package/scss/entries/dark-entry.scss +3 -0
|
@@ -55,12 +55,26 @@
|
|
|
55
55
|
--fs-control-padding-x-lg: 16px;
|
|
56
56
|
--fs-focus-width: 2px;
|
|
57
57
|
--fs-focus-offset: 2px;
|
|
58
|
+
--fs-focus-color: var(--fs-color-primary);
|
|
59
|
+
--fs-focus-ring-size: 3px;
|
|
60
|
+
--fs-focus-ring-color: color-mix(in srgb, var(--fs-focus-color) 22%, transparent);
|
|
58
61
|
--fs-focus-ring-alpha: 22%;
|
|
59
62
|
--fs-transition-fast: 150ms;
|
|
60
63
|
--fs-transition-normal: 220ms;
|
|
64
|
+
--fs-motion-duration-fast: var(--fs-transition-fast);
|
|
65
|
+
--fs-motion-duration-normal: var(--fs-transition-normal);
|
|
66
|
+
--fs-motion-ease-standard: var(--fs-ease-standard);
|
|
61
67
|
--fs-ease-standard: cubic-bezier(0.2, 0, 0, 1);
|
|
62
68
|
--fs-border-width: 1px;
|
|
63
69
|
--fs-disabled-opacity: 0.5;
|
|
70
|
+
--fs-safe-area-top: env(safe-area-inset-top, 0px);
|
|
71
|
+
--fs-safe-area-right: env(safe-area-inset-right, 0px);
|
|
72
|
+
--fs-safe-area-bottom: env(safe-area-inset-bottom, 0px);
|
|
73
|
+
--fs-safe-area-left: env(safe-area-inset-left, 0px);
|
|
74
|
+
--fs-component-control-height: var(--fs-control-height);
|
|
75
|
+
--fs-component-overlay-backdrop: rgb(0 0 0 / 55%);
|
|
76
|
+
--fs-component-panel-radius: var(--fs-radius-md);
|
|
77
|
+
--fs-component-panel-shadow: var(--fs-shadow-lg);
|
|
64
78
|
|
|
65
79
|
// Referência dos limiares usados pelos utilitários `@container` (ver
|
|
66
80
|
// `packages/fokus-utilities/scss/utilities/_container-queries.scss`).
|
|
@@ -3,6 +3,14 @@
|
|
|
3
3
|
@use "sass:color";
|
|
4
4
|
@use "../settings" as *;
|
|
5
5
|
|
|
6
|
+
@mixin fs-fluid-type($min, $max, $min-vw: 320px, $max-vw: 1280px) {
|
|
7
|
+
font-size: clamp($min, calc(#{$min} + (#{ $max } - #{ $min }) * ((100vw - #{ $min-vw }) / (#{ $max-vw } - #{ $min-vw }))), $max);
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
@mixin fs-fluid-value($property, $min, $max, $min-vw: 320px, $max-vw: 1280px) {
|
|
11
|
+
#{$property}: clamp($min, calc(#{$min} + (#{ $max } - #{ $min }) * ((100vw - #{ $min-vw }) / (#{ $max-vw } - #{ $min-vw }))), $max);
|
|
12
|
+
}
|
|
13
|
+
|
|
6
14
|
@mixin media-breakpoint-up($name) {
|
|
7
15
|
$min: map.get($breakpoints, $name);
|
|
8
16
|
|
|
@@ -7,7 +7,8 @@ let idCounter = 0;
|
|
|
7
7
|
export class Accordion {
|
|
8
8
|
constructor(accordionEl) {
|
|
9
9
|
this.accordionEl = accordionEl;
|
|
10
|
-
this.exclusive = accordionEl.getAttribute("data-multiple") !== "true";
|
|
10
|
+
this.exclusive = accordionEl.getAttribute("data-multiple") !== "true" && accordionEl.getAttribute("data-fs-always-open") !== "true";
|
|
11
|
+
this.alwaysOpen = accordionEl.getAttribute("data-fs-always-open") === "true";
|
|
11
12
|
this.items = [];
|
|
12
13
|
|
|
13
14
|
const buttons = Array.from(accordionEl.querySelectorAll(".fs-accordion-button"));
|
|
@@ -46,6 +47,7 @@ export class Accordion {
|
|
|
46
47
|
const isOpen = entry.button.getAttribute("aria-expanded") === "true";
|
|
47
48
|
|
|
48
49
|
if (isOpen) {
|
|
50
|
+
if (this.alwaysOpen) return;
|
|
49
51
|
this._close(entry);
|
|
50
52
|
return;
|
|
51
53
|
}
|
|
@@ -60,17 +62,21 @@ export class Accordion {
|
|
|
60
62
|
}
|
|
61
63
|
|
|
62
64
|
_open(entry) {
|
|
65
|
+
entry.button.dispatchEvent(new CustomEvent("fs:show", { bubbles: true }));
|
|
63
66
|
entry.button.setAttribute("aria-expanded", "true");
|
|
64
67
|
entry.panel.hidden = false;
|
|
65
68
|
expand(entry.panel).then(() => {
|
|
69
|
+
entry.button.dispatchEvent(new CustomEvent("fs:shown", { bubbles: true }));
|
|
66
70
|
entry.button.dispatchEvent(new CustomEvent("fs:accordion:shown", { bubbles: true }));
|
|
67
71
|
});
|
|
68
72
|
}
|
|
69
73
|
|
|
70
74
|
_close(entry) {
|
|
75
|
+
entry.button.dispatchEvent(new CustomEvent("fs:hide", { bubbles: true }));
|
|
71
76
|
entry.button.setAttribute("aria-expanded", "false");
|
|
72
77
|
collapse(entry.panel).then(() => {
|
|
73
78
|
entry.panel.hidden = true;
|
|
79
|
+
entry.button.dispatchEvent(new CustomEvent("fs:hidden", { bubbles: true }));
|
|
74
80
|
entry.button.dispatchEvent(new CustomEvent("fs:accordion:hidden", { bubbles: true }));
|
|
75
81
|
});
|
|
76
82
|
}
|
|
@@ -6,6 +6,23 @@ export function getFocusableElements(container) {
|
|
|
6
6
|
}
|
|
7
7
|
|
|
8
8
|
export function createFocusTrap(container) {
|
|
9
|
+
const inertSiblings = [];
|
|
10
|
+
const setSiblingsInert = (value) => {
|
|
11
|
+
if (!container.parentElement) return;
|
|
12
|
+
[...container.parentElement.children].filter((el) => el !== container).forEach((el) => {
|
|
13
|
+
if (value) {
|
|
14
|
+
inertSiblings.push({ el, value: el.inert, ariaHidden: el.getAttribute("aria-hidden") });
|
|
15
|
+
el.inert = true;
|
|
16
|
+
el.setAttribute("aria-hidden", "true");
|
|
17
|
+
} else {
|
|
18
|
+
const previous = inertSiblings.find((item) => item.el === el);
|
|
19
|
+
if (previous) {
|
|
20
|
+
el.inert = previous.value;
|
|
21
|
+
previous.ariaHidden === null ? el.removeAttribute("aria-hidden") : el.setAttribute("aria-hidden", previous.ariaHidden);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
});
|
|
25
|
+
};
|
|
9
26
|
function handleKeydown(event) {
|
|
10
27
|
if (event.key !== "Tab") return;
|
|
11
28
|
|
|
@@ -32,11 +49,14 @@ export function createFocusTrap(container) {
|
|
|
32
49
|
|
|
33
50
|
return {
|
|
34
51
|
activate() {
|
|
52
|
+
setSiblingsInert(true);
|
|
35
53
|
const focusable = getFocusableElements(container);
|
|
36
54
|
if (focusable.length > 0) focusable[0].focus();
|
|
37
55
|
},
|
|
38
56
|
deactivate() {
|
|
39
57
|
container.removeEventListener("keydown", handleKeydown);
|
|
58
|
+
setSiblingsInert(false);
|
|
59
|
+
inertSiblings.length = 0;
|
|
40
60
|
},
|
|
41
61
|
};
|
|
42
62
|
}
|
|
@@ -1,3 +1,5 @@
|
|
|
1
1
|
export function lockScroll(): void;
|
|
2
2
|
export function unlockScroll(): void;
|
|
3
3
|
export function onClickOutside(el: Element, callback: (event: MouseEvent) => void): () => void;
|
|
4
|
+
export function registerOverlay(instance: { hide?: () => void }): void;
|
|
5
|
+
export function unregisterOverlay(instance: { hide?: () => void }): void;
|
|
@@ -2,6 +2,16 @@ let lockCount = 0;
|
|
|
2
2
|
let paddingCompensated = false;
|
|
3
3
|
let previousOverflow = "";
|
|
4
4
|
let previousPaddingRight = "";
|
|
5
|
+
const activeOverlays = [];
|
|
6
|
+
|
|
7
|
+
export function registerOverlay(instance) {
|
|
8
|
+
if (!activeOverlays.includes(instance)) activeOverlays.push(instance);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function unregisterOverlay(instance) {
|
|
12
|
+
const index = activeOverlays.lastIndexOf(instance);
|
|
13
|
+
if (index >= 0) activeOverlays.splice(index, 1);
|
|
14
|
+
}
|
|
5
15
|
|
|
6
16
|
export function lockScroll() {
|
|
7
17
|
lockCount += 1;
|
|
@@ -14,11 +14,13 @@ export function createInstanceRegistry() {
|
|
|
14
14
|
};
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
-
export function autoInit(name, Ctor) {
|
|
17
|
+
export function autoInit(name, Ctor, options = {}) {
|
|
18
18
|
if (typeof document === "undefined") return;
|
|
19
19
|
|
|
20
|
+
const selector = options.selector ?? `[data-fs="${name}"]`;
|
|
21
|
+
|
|
20
22
|
const init = () => {
|
|
21
|
-
document.querySelectorAll(
|
|
23
|
+
document.querySelectorAll(selector).forEach((el) => {
|
|
22
24
|
if (!Ctor.getInstance(el)) new Ctor(el);
|
|
23
25
|
});
|
|
24
26
|
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { createFocusTrap, getFocusableElements, onEscapeKey } from "./core/focus.js";
|
|
@@ -24,3 +24,10 @@ export { CommandPalette } from "./command-palette.js";
|
|
|
24
24
|
export { TreeView } from "./tree-view.js";
|
|
25
25
|
export { RangeSlider } from "./range.js";
|
|
26
26
|
export { FileUploadAdvanced } from "./file-upload-advanced.js";
|
|
27
|
+
export { Theme } from "./theme.js";
|
|
28
|
+
export { Navbar } from "./navbar.js";
|
|
29
|
+
export { Scrollspy } from "./scrollspy.js";
|
|
30
|
+
export { FormValidation } from "./form-validation.js";
|
|
31
|
+
export { createFocusTrap, getFocusableElements, onEscapeKey } from "./focus-trap.js";
|
|
32
|
+
export { Spinner } from "./spinner.js";
|
|
33
|
+
export { Pagination } from "./pagination.js";
|
|
@@ -24,3 +24,10 @@ export { CommandPalette } from "./command-palette.js";
|
|
|
24
24
|
export { TreeView } from "./tree-view.js";
|
|
25
25
|
export { RangeSlider } from "./range.js";
|
|
26
26
|
export { FileUploadAdvanced } from "./file-upload-advanced.js";
|
|
27
|
+
export { Theme } from "./theme.js";
|
|
28
|
+
export { Navbar } from "./navbar.js";
|
|
29
|
+
export { Scrollspy } from "./scrollspy.js";
|
|
30
|
+
export { FormValidation } from "./form-validation.js";
|
|
31
|
+
export { Spinner } from "./spinner.js";
|
|
32
|
+
export { Pagination } from "./pagination.js";
|
|
33
|
+
export { createFocusTrap, getFocusableElements, onEscapeKey } from "./focus-trap.js";
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export declare class FormValidation {
|
|
2
|
+
constructor(form: HTMLFormElement);
|
|
3
|
+
static getInstance(form: HTMLFormElement): FormValidation | undefined;
|
|
4
|
+
static getOrCreateInstance(form: HTMLFormElement): FormValidation;
|
|
5
|
+
validate(event: SubmitEvent): boolean;
|
|
6
|
+
dispose(): void;
|
|
7
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { autoInit, createInstanceRegistry } from "./core/register.js";
|
|
2
|
+
|
|
3
|
+
const instances = createInstanceRegistry();
|
|
4
|
+
|
|
5
|
+
export class FormValidation {
|
|
6
|
+
constructor(form) { this.form = form; this._submit = (event) => this.validate(event); form.addEventListener("submit", this._submit); instances.set(form, this); }
|
|
7
|
+
static getInstance(el) { return instances.get(el); }
|
|
8
|
+
static getOrCreateInstance(el) { return this.getInstance(el) ?? new FormValidation(el); }
|
|
9
|
+
validate(event) {
|
|
10
|
+
const valid = this.form.checkValidity();
|
|
11
|
+
this.form.classList.add("fs-was-validated");
|
|
12
|
+
this.form.querySelectorAll('[required], [aria-required="true"]').forEach((field) => field.setAttribute("aria-invalid", String(!field.checkValidity())));
|
|
13
|
+
if (!valid) { event.preventDefault(); event.stopPropagation(); }
|
|
14
|
+
this.form.dispatchEvent(new CustomEvent("fs:form:validated", { bubbles: true, detail: { valid } }));
|
|
15
|
+
return valid;
|
|
16
|
+
}
|
|
17
|
+
dispose() { this.form.removeEventListener("submit", this._submit); instances.delete(this.form); }
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
autoInit("form-validation", FormValidation);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { lockScroll, unlockScroll, onClickOutside } from "./core/overlay.js";
|
|
1
|
+
import { lockScroll, unlockScroll, onClickOutside, registerOverlay, unregisterOverlay } from "./core/overlay.js";
|
|
2
2
|
import { createFocusTrap, onEscapeKey } from "./core/focus.js";
|
|
3
3
|
import { autoInit, createInstanceRegistry } from "./core/register.js";
|
|
4
4
|
|
|
@@ -47,6 +47,14 @@ export class Modal {
|
|
|
47
47
|
return instances.get(el);
|
|
48
48
|
}
|
|
49
49
|
|
|
50
|
+
static getOrCreateInstance(el, options = {}) {
|
|
51
|
+
return this.getInstance(el) ?? new Modal(el, options);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
static show(el, options = {}) { return this.getOrCreateInstance(el, options).show(); }
|
|
55
|
+
static hide(el) { return this.getInstance(el)?.hide(); }
|
|
56
|
+
static toggle(el, options = {}) { return this.getOrCreateInstance(el, options).toggle(); }
|
|
57
|
+
|
|
50
58
|
_handleTriggerClick(event) {
|
|
51
59
|
event.preventDefault();
|
|
52
60
|
this.show();
|
|
@@ -60,7 +68,9 @@ export class Modal {
|
|
|
60
68
|
|
|
61
69
|
show() {
|
|
62
70
|
if (this.isOpen) return;
|
|
71
|
+
this.triggerEl.dispatchEvent(new CustomEvent("fs:show", { bubbles: true }));
|
|
63
72
|
this.isOpen = true;
|
|
73
|
+
registerOverlay(this);
|
|
64
74
|
|
|
65
75
|
this.modalEl.classList.add("is-open");
|
|
66
76
|
this.modalEl.setAttribute("aria-hidden", "false");
|
|
@@ -73,12 +83,15 @@ export class Modal {
|
|
|
73
83
|
this._outsideClickCleanup = onClickOutside(this.dialogEl, () => this.hide());
|
|
74
84
|
}
|
|
75
85
|
|
|
86
|
+
this.triggerEl.dispatchEvent(new CustomEvent("fs:shown", { bubbles: true }));
|
|
76
87
|
this.triggerEl.dispatchEvent(new CustomEvent("fs:modal:shown", { bubbles: true }));
|
|
77
88
|
}
|
|
78
89
|
|
|
79
90
|
hide() {
|
|
80
91
|
if (!this.isOpen) return;
|
|
92
|
+
this.triggerEl.dispatchEvent(new CustomEvent("fs:hide", { bubbles: true }));
|
|
81
93
|
this.isOpen = false;
|
|
94
|
+
unregisterOverlay(this);
|
|
82
95
|
|
|
83
96
|
this.modalEl.classList.remove("is-open");
|
|
84
97
|
this.modalEl.setAttribute("aria-hidden", "true");
|
|
@@ -92,6 +105,7 @@ export class Modal {
|
|
|
92
105
|
|
|
93
106
|
this.triggerEl.focus();
|
|
94
107
|
|
|
108
|
+
this.triggerEl.dispatchEvent(new CustomEvent("fs:hidden", { bubbles: true }));
|
|
95
109
|
this.triggerEl.dispatchEvent(new CustomEvent("fs:modal:hidden", { bubbles: true }));
|
|
96
110
|
}
|
|
97
111
|
|
|
@@ -112,4 +126,4 @@ export class Modal {
|
|
|
112
126
|
}
|
|
113
127
|
}
|
|
114
128
|
|
|
115
|
-
autoInit("modal", Modal);
|
|
129
|
+
autoInit("modal", Modal, { selector: '[data-fs="modal"], [data-fs-toggle="modal"]' });
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export declare class Navbar {
|
|
2
|
+
constructor(element: Element);
|
|
3
|
+
static getInstance(element: Element): Navbar | undefined;
|
|
4
|
+
static getOrCreateInstance(element: Element): Navbar;
|
|
5
|
+
isOpen(): boolean;
|
|
6
|
+
show(): void;
|
|
7
|
+
hide(): void;
|
|
8
|
+
toggle(): void;
|
|
9
|
+
dispose(): void;
|
|
10
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { autoInit, createInstanceRegistry } from "./core/register.js";
|
|
2
|
+
|
|
3
|
+
const instances = createInstanceRegistry();
|
|
4
|
+
|
|
5
|
+
export class Navbar {
|
|
6
|
+
constructor(navbarEl) {
|
|
7
|
+
this.navbarEl = navbarEl;
|
|
8
|
+
this.toggleEl = navbarEl.querySelector('[data-fs-toggle="navbar"]');
|
|
9
|
+
const target = this.toggleEl?.getAttribute("data-fs-target");
|
|
10
|
+
this.menuEl = target ? document.querySelector(target) : navbarEl.querySelector(".fs-navbar-collapse");
|
|
11
|
+
if (!this.toggleEl || !this.menuEl) return;
|
|
12
|
+
this._toggle = () => this.toggle();
|
|
13
|
+
this._escape = (event) => { if (event.key === "Escape" && this.isOpen()) this.hide(); };
|
|
14
|
+
this.toggleEl.addEventListener("click", this._toggle);
|
|
15
|
+
this.menuEl.addEventListener("keydown", this._escape);
|
|
16
|
+
instances.set(navbarEl, this);
|
|
17
|
+
}
|
|
18
|
+
static getInstance(el) { return instances.get(el); }
|
|
19
|
+
static getOrCreateInstance(el) { return this.getInstance(el) ?? new Navbar(el); }
|
|
20
|
+
isOpen() { return this.menuEl?.classList.contains("is-open"); }
|
|
21
|
+
show() { this.menuEl.classList.add("is-open"); this.menuEl.hidden = false; this.toggleEl.setAttribute("aria-expanded", "true"); this.navbarEl.dispatchEvent(new CustomEvent("fs:shown", { bubbles: true })); }
|
|
22
|
+
hide() { this.menuEl.classList.remove("is-open"); this.menuEl.hidden = true; this.toggleEl.setAttribute("aria-expanded", "false"); this.toggleEl.focus(); this.navbarEl.dispatchEvent(new CustomEvent("fs:hidden", { bubbles: true })); }
|
|
23
|
+
toggle() { this.isOpen() ? this.hide() : this.show(); }
|
|
24
|
+
dispose() { this.toggleEl?.removeEventListener("click", this._toggle); this.menuEl?.removeEventListener("keydown", this._escape); instances.delete(this.navbarEl); }
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
autoInit("navbar", Navbar);
|
|
@@ -12,6 +12,10 @@ export class Offcanvas {
|
|
|
12
12
|
constructor(triggerEl: HTMLElement, options?: OffcanvasOptions);
|
|
13
13
|
|
|
14
14
|
static getInstance(el: Element): Offcanvas | undefined;
|
|
15
|
+
static getOrCreateInstance(el: HTMLElement, options?: OffcanvasOptions): Offcanvas;
|
|
16
|
+
static show(el: HTMLElement, options?: OffcanvasOptions): void;
|
|
17
|
+
static hide(el: HTMLElement): void;
|
|
18
|
+
static toggle(el: HTMLElement, options?: OffcanvasOptions): void;
|
|
15
19
|
|
|
16
20
|
show(): void;
|
|
17
21
|
hide(): void;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { lockScroll, unlockScroll, onClickOutside } from "./core/overlay.js";
|
|
1
|
+
import { lockScroll, unlockScroll, onClickOutside, registerOverlay, unregisterOverlay } from "./core/overlay.js";
|
|
2
2
|
import { createFocusTrap, onEscapeKey } from "./core/focus.js";
|
|
3
3
|
import { autoInit, createInstanceRegistry } from "./core/register.js";
|
|
4
4
|
|
|
@@ -48,6 +48,14 @@ export class Offcanvas {
|
|
|
48
48
|
return instances.get(el);
|
|
49
49
|
}
|
|
50
50
|
|
|
51
|
+
static getOrCreateInstance(el, options = {}) {
|
|
52
|
+
return this.getInstance(el) ?? new Offcanvas(el, options);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
static show(el, options = {}) { return this.getOrCreateInstance(el, options).show(); }
|
|
56
|
+
static hide(el) { return this.getInstance(el)?.hide(); }
|
|
57
|
+
static toggle(el, options = {}) { return this.getOrCreateInstance(el, options).toggle(); }
|
|
58
|
+
|
|
51
59
|
_handleTriggerClick(event) {
|
|
52
60
|
event.preventDefault();
|
|
53
61
|
this.show();
|
|
@@ -61,7 +69,9 @@ export class Offcanvas {
|
|
|
61
69
|
|
|
62
70
|
show() {
|
|
63
71
|
if (this.isOpen) return;
|
|
72
|
+
this.triggerEl.dispatchEvent(new CustomEvent("fs:show", { bubbles: true }));
|
|
64
73
|
this.isOpen = true;
|
|
74
|
+
registerOverlay(this);
|
|
65
75
|
|
|
66
76
|
if (this.hasBackdrop) {
|
|
67
77
|
this._backdropEl = document.createElement("div");
|
|
@@ -96,12 +106,15 @@ export class Offcanvas {
|
|
|
96
106
|
});
|
|
97
107
|
}
|
|
98
108
|
|
|
109
|
+
this.triggerEl.dispatchEvent(new CustomEvent("fs:shown", { bubbles: true }));
|
|
99
110
|
this.triggerEl.dispatchEvent(new CustomEvent("fs:offcanvas:shown", { bubbles: true }));
|
|
100
111
|
}
|
|
101
112
|
|
|
102
113
|
hide() {
|
|
103
114
|
if (!this.isOpen) return;
|
|
115
|
+
this.triggerEl.dispatchEvent(new CustomEvent("fs:hide", { bubbles: true }));
|
|
104
116
|
this.isOpen = false;
|
|
117
|
+
unregisterOverlay(this);
|
|
105
118
|
|
|
106
119
|
this.offcanvasEl.classList.remove("is-open");
|
|
107
120
|
this.offcanvasEl.setAttribute("aria-hidden", "true");
|
|
@@ -118,6 +131,7 @@ export class Offcanvas {
|
|
|
118
131
|
|
|
119
132
|
this.triggerEl.focus();
|
|
120
133
|
|
|
134
|
+
this.triggerEl.dispatchEvent(new CustomEvent("fs:hidden", { bubbles: true }));
|
|
121
135
|
this.triggerEl.dispatchEvent(new CustomEvent("fs:offcanvas:hidden", { bubbles: true }));
|
|
122
136
|
}
|
|
123
137
|
|
|
@@ -138,4 +152,4 @@ export class Offcanvas {
|
|
|
138
152
|
}
|
|
139
153
|
}
|
|
140
154
|
|
|
141
|
-
autoInit("offcanvas", Offcanvas);
|
|
155
|
+
autoInit("offcanvas", Offcanvas, { selector: '[data-fs="offcanvas"], [data-fs-toggle="offcanvas"]' });
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { autoInit, createInstanceRegistry } from "./core/register.js";
|
|
2
|
+
|
|
3
|
+
const instances = createInstanceRegistry();
|
|
4
|
+
export class Pagination {
|
|
5
|
+
constructor(element) {
|
|
6
|
+
this.element = element;
|
|
7
|
+
this.current = Number(element.getAttribute("data-fs-page")) || 1;
|
|
8
|
+
this.total = Number(element.getAttribute("data-fs-pages")) || element.querySelectorAll("[data-fs-page]").length || 1;
|
|
9
|
+
this._click = (event) => {
|
|
10
|
+
const button = event.target.closest("[data-fs-page]");
|
|
11
|
+
if (!button || !this.element.contains(button)) return;
|
|
12
|
+
event.preventDefault();
|
|
13
|
+
this.goTo(Number(button.getAttribute("data-fs-page")));
|
|
14
|
+
};
|
|
15
|
+
element.addEventListener("click", this._click);
|
|
16
|
+
instances.set(element, this);
|
|
17
|
+
this._render();
|
|
18
|
+
}
|
|
19
|
+
static getInstance(el) { return instances.get(el); }
|
|
20
|
+
static getOrCreateInstance(el) { return this.getInstance(el) ?? new Pagination(el); }
|
|
21
|
+
goTo(page) {
|
|
22
|
+
const next = Math.max(1, Math.min(this.total, page));
|
|
23
|
+
if (next === this.current) return;
|
|
24
|
+
this.current = next;
|
|
25
|
+
this.element.setAttribute("data-fs-page", String(next));
|
|
26
|
+
this._render();
|
|
27
|
+
this.element.dispatchEvent(new CustomEvent("fs:pagination:change", { bubbles: true, detail: { page: next, total: this.total } }));
|
|
28
|
+
}
|
|
29
|
+
_render() {
|
|
30
|
+
this.element.querySelectorAll("[data-fs-page]").forEach((item) => {
|
|
31
|
+
const active = Number(item.getAttribute("data-fs-page")) === this.current;
|
|
32
|
+
item.classList.toggle("is-active", active);
|
|
33
|
+
item.setAttribute("aria-current", active ? "page" : "false");
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
dispose() { this.element.removeEventListener("click", this._click); instances.delete(this.element); }
|
|
37
|
+
}
|
|
38
|
+
autoInit("pagination", Pagination);
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { autoInit, createInstanceRegistry } from "./core/register.js";
|
|
2
|
+
|
|
3
|
+
const instances = createInstanceRegistry();
|
|
4
|
+
|
|
5
|
+
export class Scrollspy {
|
|
6
|
+
constructor(navEl) {
|
|
7
|
+
this.navEl = navEl;
|
|
8
|
+
this.links = [...navEl.querySelectorAll('a[href^="#"]')];
|
|
9
|
+
this.sections = this.links.map((link) => document.querySelector(link.getAttribute("href"))).filter(Boolean);
|
|
10
|
+
this.observer = typeof IntersectionObserver === "undefined" ? null : new IntersectionObserver((entries) => entries.forEach((entry) => entry.isIntersecting && this.activate(entry.target)), { rootMargin: "-20% 0px -65%" });
|
|
11
|
+
this.sections.forEach((section) => this.observer?.observe(section));
|
|
12
|
+
this._onScroll = () => {
|
|
13
|
+
if (this.observer) return;
|
|
14
|
+
const current = this.sections.filter((section) => section.getBoundingClientRect().top <= window.innerHeight * 0.35).at(-1);
|
|
15
|
+
if (current) this.activate(current);
|
|
16
|
+
};
|
|
17
|
+
if (!this.observer) window.addEventListener("scroll", this._onScroll, { passive: true });
|
|
18
|
+
instances.set(navEl, this);
|
|
19
|
+
}
|
|
20
|
+
static getInstance(el) { return instances.get(el); }
|
|
21
|
+
static getOrCreateInstance(el) { return this.getInstance(el) ?? new Scrollspy(el); }
|
|
22
|
+
activate(section) { this.links.forEach((link) => link.classList.toggle("is-active", link.getAttribute("href") === `#${section.id}`)); this.navEl.dispatchEvent(new CustomEvent("fs:scrollspy:activate", { bubbles: true, detail: { section } })); }
|
|
23
|
+
dispose() { this.observer?.disconnect(); window.removeEventListener("scroll", this._onScroll); instances.delete(this.navEl); }
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
autoInit("scrollspy", Scrollspy);
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export declare class Spinner {
|
|
2
|
+
constructor(element: HTMLElement);
|
|
3
|
+
static getInstance(element: HTMLElement): Spinner | undefined;
|
|
4
|
+
static getOrCreateInstance(element: HTMLElement): Spinner;
|
|
5
|
+
start(): void;
|
|
6
|
+
stop(): void;
|
|
7
|
+
toggle(): void;
|
|
8
|
+
dispose(): void;
|
|
9
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { autoInit, createInstanceRegistry } from "./core/register.js";
|
|
2
|
+
|
|
3
|
+
const instances = createInstanceRegistry();
|
|
4
|
+
export class Spinner {
|
|
5
|
+
constructor(element) {
|
|
6
|
+
this.element = element;
|
|
7
|
+
this._setLoading(element.hasAttribute("data-fs-loading") && element.getAttribute("data-fs-loading") !== "false");
|
|
8
|
+
instances.set(element, this);
|
|
9
|
+
}
|
|
10
|
+
static getInstance(el) { return instances.get(el); }
|
|
11
|
+
static getOrCreateInstance(el) { return this.getInstance(el) ?? new Spinner(el); }
|
|
12
|
+
_setLoading(value) { this.loading = value; this.element.toggleAttribute("aria-busy", value); this.element.toggleAttribute("data-fs-loading", value); }
|
|
13
|
+
start() { this._setLoading(true); this.element.dispatchEvent(new CustomEvent("fs:loading:start", { bubbles: true })); }
|
|
14
|
+
stop() { this._setLoading(false); this.element.dispatchEvent(new CustomEvent("fs:loading:stop", { bubbles: true })); }
|
|
15
|
+
toggle() { this.loading ? this.stop() : this.start(); }
|
|
16
|
+
dispose() { instances.delete(this.element); }
|
|
17
|
+
}
|
|
18
|
+
autoInit("spinner", Spinner);
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export declare class Theme {
|
|
2
|
+
constructor(root?: Element);
|
|
3
|
+
static getInstance(root?: Element): Theme | undefined;
|
|
4
|
+
static getOrCreateInstance(root?: Element): Theme;
|
|
5
|
+
static set(mode: "light" | "dark" | "auto", root?: Element): Theme;
|
|
6
|
+
get(): string;
|
|
7
|
+
set(mode: "light" | "dark" | "auto"): Theme;
|
|
8
|
+
dispose(): void;
|
|
9
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { createInstanceRegistry } from "./core/register.js";
|
|
2
|
+
|
|
3
|
+
const instances = createInstanceRegistry();
|
|
4
|
+
|
|
5
|
+
export class Theme {
|
|
6
|
+
constructor(root = document.documentElement) { this.root = root; instances.set(root, this); }
|
|
7
|
+
static getInstance(root = document.documentElement) { return instances.get(root); }
|
|
8
|
+
static getOrCreateInstance(root = document.documentElement) { return this.getInstance(root) ?? new Theme(root); }
|
|
9
|
+
static set(mode, root = document.documentElement) { return this.getOrCreateInstance(root).set(mode); }
|
|
10
|
+
get() { return this.root.getAttribute("data-fs-theme") ?? this.root.getAttribute("data-theme") ?? "light"; }
|
|
11
|
+
set(mode) {
|
|
12
|
+
if (!["light", "dark", "auto"].includes(mode)) throw new TypeError("FokusStyles.Theme: modo inválido.");
|
|
13
|
+
this.root.setAttribute("data-fs-theme", mode);
|
|
14
|
+
this.root.setAttribute("data-theme", mode === "dark" ? "dark" : "light");
|
|
15
|
+
this.root.dispatchEvent(new CustomEvent("fs:theme:change", { bubbles: true, detail: { mode } }));
|
|
16
|
+
return this;
|
|
17
|
+
}
|
|
18
|
+
dispose() { instances.delete(this.root); }
|
|
19
|
+
}
|
|
@@ -33,9 +33,11 @@ export class Toast {
|
|
|
33
33
|
|
|
34
34
|
show() {
|
|
35
35
|
if (this.isOpen) return;
|
|
36
|
+
this.toastEl.dispatchEvent(new CustomEvent("fs:show", { bubbles: true }));
|
|
36
37
|
this.isOpen = true;
|
|
37
38
|
|
|
38
39
|
expand(this.toastEl).then(() => {
|
|
40
|
+
this.toastEl.dispatchEvent(new CustomEvent("fs:shown", { bubbles: true }));
|
|
39
41
|
this.toastEl.dispatchEvent(new CustomEvent("fs:toast:shown", { bubbles: true }));
|
|
40
42
|
});
|
|
41
43
|
|
|
@@ -46,12 +48,14 @@ export class Toast {
|
|
|
46
48
|
|
|
47
49
|
hide() {
|
|
48
50
|
if (!this.isOpen) return;
|
|
51
|
+
this.toastEl.dispatchEvent(new CustomEvent("fs:hide", { bubbles: true }));
|
|
49
52
|
this.isOpen = false;
|
|
50
53
|
|
|
51
54
|
clearTimeout(this._hideTimer);
|
|
52
55
|
this._hideTimer = null;
|
|
53
56
|
|
|
54
57
|
collapse(this.toastEl).then(() => {
|
|
58
|
+
this.toastEl.dispatchEvent(new CustomEvent("fs:hidden", { bubbles: true }));
|
|
55
59
|
this.toastEl.dispatchEvent(new CustomEvent("fs:toast:hidden", { bubbles: true }));
|
|
56
60
|
});
|
|
57
61
|
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
@use "sass:map";
|
|
2
|
+
@use "../../fokus-core/scss/settings" as *;
|
|
3
|
+
@use "../../fokus-core/scss/tools" as tools;
|
|
4
|
+
|
|
5
|
+
// Utility API do Fokus Styles: os mapas são públicos e podem ser estendidos
|
|
6
|
+
// antes do @use do entry-point pelo consumidor.
|
|
7
|
+
$fs-utility-colors: $theme-colors !default;
|
|
8
|
+
$fs-utility-spacers: $spacers !default;
|
|
9
|
+
$fs-utility-sizes: (25: 25%, 50: 50%, 75: 75%, 100: 100%, auto: auto) !default;
|
|
10
|
+
|
|
11
|
+
@layer utilities {
|
|
12
|
+
@if fs-config-list-enabled(colors, color) {
|
|
13
|
+
@each $name, $value in $fs-utility-colors {
|
|
14
|
+
.fs-u-color-#{$name} { color: var(--fs-color-#{$name}, #{$value}) !important; }
|
|
15
|
+
.fs-u-bg-#{$name} { background-color: var(--fs-color-#{$name}, #{$value}) !important; }
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
@if fs-config-list-enabled(spacing, margin) and fs-config-list-enabled(spacing, padding) {
|
|
20
|
+
@each $name, $value in $fs-utility-spacers {
|
|
21
|
+
.fs-u-m-#{$name} { margin: $value !important; }
|
|
22
|
+
.fs-u-mx-#{$name} { margin-inline: $value !important; }
|
|
23
|
+
.fs-u-my-#{$name} { margin-block: $value !important; }
|
|
24
|
+
.fs-u-p-#{$name} { padding: $value !important; }
|
|
25
|
+
.fs-u-px-#{$name} { padding-inline: $value !important; }
|
|
26
|
+
.fs-u-py-#{$name} { padding-block: $value !important; }
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
@if fs-config-list-enabled(sizing, width) {
|
|
31
|
+
@each $name, $value in $fs-utility-sizes {
|
|
32
|
+
.fs-u-w-#{$name} { width: $value !important; }
|
|
33
|
+
.fs-u-h-#{$name} { height: $value !important; }
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
@if fs-config-get(responsive, true) {
|
|
38
|
+
@each $breakpoint, $min in $breakpoints {
|
|
39
|
+
@if $min != 0 {
|
|
40
|
+
@media (min-width: $min) {
|
|
41
|
+
.fs-u-d-#{$breakpoint}-flex { display: flex !important; }
|
|
42
|
+
.fs-u-d-#{$breakpoint}-none { display: none !important; }
|
|
43
|
+
.fs-u-text-#{$breakpoint}-start { text-align: start !important; }
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
@layer utilities {
|
|
2
|
+
.fs-u-focus-ring:focus-visible {
|
|
3
|
+
outline: var(--fs-focus-width) solid var(--fs-focus-color);
|
|
4
|
+
outline-offset: var(--fs-focus-offset);
|
|
5
|
+
box-shadow: 0 0 0 var(--fs-focus-ring-size) var(--fs-focus-ring-color);
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
.fs-u-disabled,
|
|
9
|
+
.fs-u-disabled[aria-disabled="true"] {
|
|
10
|
+
cursor: not-allowed;
|
|
11
|
+
opacity: var(--fs-disabled-opacity);
|
|
12
|
+
pointer-events: none;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
@media print {
|
|
16
|
+
.fs-u-print-hidden { display: none !important; }
|
|
17
|
+
.fs-u-print-visible { display: block !important; }
|
|
18
|
+
}
|
|
19
|
+
}
|