drab 6.2.2 → 6.4.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/announcer/index.d.ts +2 -0
- package/dist/announcer/index.js +3 -1
- package/dist/base/index.d.ts +2896 -67
- package/dist/base/index.js +68 -52
- package/dist/copy/index.d.ts +8 -1
- package/dist/copy/index.js +8 -1
- package/dist/dialog/index.d.ts +2 -0
- package/dist/dialog/index.js +2 -0
- package/dist/editor/index.d.ts +17 -9
- package/dist/editor/index.js +17 -9
- package/dist/index.d.ts +2 -1
- package/dist/index.js +2 -1
- package/dist/intersect/index.d.ts +2 -0
- package/dist/intersect/index.js +2 -0
- package/dist/prefetch/index.d.ts +2 -0
- package/dist/prefetch/index.js +4 -2
- package/dist/share/index.d.ts +8 -1
- package/dist/share/index.js +8 -1
- package/dist/tablesort/index.js +5 -2
- package/dist/tabs/define.d.ts +1 -0
- package/dist/tabs/define.js +3 -0
- package/dist/tabs/index.d.ts +37 -0
- package/dist/tabs/index.js +147 -0
- package/dist/util/validate.d.ts +7 -0
- package/dist/util/validate.js +10 -0
- package/dist/wakelock/index.d.ts +13 -3
- package/dist/wakelock/index.js +13 -3
- package/package.json +1 -1
package/dist/base/index.js
CHANGED
@@ -1,26 +1,8 @@
|
|
1
1
|
import { Announcer } from "../announcer/index.js";
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
* By default, `trigger`s and `content` will be selected via the `data-trigger` and
|
7
|
-
* `data-content` attributes. Alternatively, you can set the `trigger` or
|
8
|
-
* `content` attribute to a CSS selector to change the default selector from
|
9
|
-
* `[data-trigger]` or `[data-content]` to a selector of your choosing.
|
10
|
-
* This can be useful if you have multiple elements within one another.
|
11
|
-
*
|
12
|
-
* Each element can have multiple `trigger`s, but will only have one `content`.
|
13
|
-
*/
|
14
|
-
export class Base extends HTMLElement {
|
15
|
-
/**
|
16
|
-
* A single `Announcer` element to share between all drab elements to announce
|
17
|
-
* interactive changes.
|
18
|
-
*/
|
19
|
-
static #announcer = Announcer.init();
|
20
|
-
/** To clean up event listeners added to `document` when the element is removed. */
|
21
|
-
#listenerController = new AbortController();
|
22
|
-
constructor() {
|
23
|
-
super();
|
2
|
+
import { validate } from "../util/validate.js";
|
3
|
+
export const Trigger = (Super) => class Trigger extends Super {
|
4
|
+
constructor(...args) {
|
5
|
+
super(args);
|
24
6
|
}
|
25
7
|
/**
|
26
8
|
* Event for the `trigger` to execute.
|
@@ -30,35 +12,32 @@ export class Base extends HTMLElement {
|
|
30
12
|
* @default "click"
|
31
13
|
*/
|
32
14
|
get event() {
|
33
|
-
return this.getAttribute("event") ?? "click";
|
15
|
+
return (this.getAttribute("event") ?? "click");
|
34
16
|
}
|
35
17
|
set event(value) {
|
36
18
|
this.setAttribute("event", value);
|
37
19
|
}
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
20
|
+
getTrigger(instance = HTMLElement) {
|
21
|
+
const triggers = this.querySelectorAll(this.getAttribute("trigger") ?? "[data-trigger]");
|
22
|
+
for (const trigger of triggers)
|
23
|
+
validate(trigger, instance);
|
24
|
+
return triggers;
|
43
25
|
}
|
44
26
|
/**
|
45
|
-
* @
|
46
|
-
* @default this.querySelectorAll("[data-trigger]")
|
27
|
+
* @param listener Listener to attach to all of the `trigger` elements.
|
47
28
|
*/
|
48
|
-
|
49
|
-
|
29
|
+
triggerListener(listener, type = this.event, options) {
|
30
|
+
for (const trigger of this.getTrigger()) {
|
31
|
+
trigger.addEventListener(type, listener, options);
|
32
|
+
}
|
33
|
+
}
|
34
|
+
};
|
35
|
+
export const Content = (Super) => class Content extends Super {
|
36
|
+
constructor(...args) {
|
37
|
+
super(args);
|
50
38
|
}
|
51
|
-
/**
|
52
|
-
* @param instance The instance of the desired element to validate against,
|
53
|
-
* ex: `HTMLDialogElement`. Defaults to `HTMLElement`.
|
54
|
-
* @returns The element that matches the `content` selector.
|
55
|
-
* @default this.querySelector("[data-content]")
|
56
|
-
*/
|
57
39
|
getContent(instance = HTMLElement) {
|
58
|
-
|
59
|
-
if (content instanceof instance)
|
60
|
-
return content;
|
61
|
-
throw new Error("Content not found");
|
40
|
+
return validate(this.querySelector(this.getAttribute("content") ?? "[data-content]"), instance);
|
62
41
|
}
|
63
42
|
/**
|
64
43
|
* Finds the `HTMLElement | HTMLTemplateElement` via the `swap` selector and
|
@@ -98,22 +77,26 @@ export class Base extends HTMLElement {
|
|
98
77
|
}
|
99
78
|
}
|
100
79
|
}
|
80
|
+
};
|
81
|
+
export const Lifecycle = (Super) => class Lifecycle extends Super {
|
82
|
+
/** To clean up event listeners added to `document` when the element is removed. */
|
83
|
+
#listenerController = new AbortController();
|
84
|
+
constructor(...args) {
|
85
|
+
super(args);
|
86
|
+
}
|
101
87
|
safeListener(type, listener, target = document.body, options = {}) {
|
102
88
|
options.signal = this.#listenerController.signal;
|
103
89
|
target.addEventListener(type, listener, options);
|
104
90
|
}
|
105
91
|
/**
|
106
|
-
*
|
107
|
-
|
108
|
-
triggerListener(listener, type = this.event, options) {
|
109
|
-
for (const trigger of this.getTrigger()) {
|
110
|
-
trigger.addEventListener(type, listener, options);
|
111
|
-
}
|
112
|
-
}
|
113
|
-
/**
|
114
|
-
* Passed into `queueMicrotask` in `connectedCallback`. It is overridden in each component that needs to run `connectedCallback`.
|
92
|
+
* Passed into `queueMicrotask` in `connectedCallback`.
|
93
|
+
* It is overridden in each component that needs to run `connectedCallback`.
|
115
94
|
*
|
116
|
-
* The reason for this is to make these elements work better with frameworks like Svelte.
|
95
|
+
* The reason for this is to make these elements work better with frameworks like Svelte.
|
96
|
+
* For SSR this isn't necessary, but when client side rendering, the HTML within the
|
97
|
+
* custom element isn't available before `connectedCallback` is called. By waiting until
|
98
|
+
* the next microtask, the HTML content is available---then for example, listeners can
|
99
|
+
* be attached to elements inside.
|
117
100
|
*/
|
118
101
|
mount() { }
|
119
102
|
/** Called when custom element is added to the page. */
|
@@ -129,4 +112,37 @@ export class Base extends HTMLElement {
|
|
129
112
|
this.destroy();
|
130
113
|
this.#listenerController.abort();
|
131
114
|
}
|
115
|
+
};
|
116
|
+
export const Announce = (Super) => class Announce extends Super {
|
117
|
+
/**
|
118
|
+
* A single `Announcer` element to share between all drab elements to announce
|
119
|
+
* interactive changes.
|
120
|
+
*/
|
121
|
+
static #announcer = Announcer.init();
|
122
|
+
constructor(...args) {
|
123
|
+
super(args);
|
124
|
+
}
|
125
|
+
/**
|
126
|
+
* @param message message to announce to screen readers
|
127
|
+
*/
|
128
|
+
announce(message) {
|
129
|
+
Announce.#announcer.announce(message);
|
130
|
+
}
|
131
|
+
};
|
132
|
+
/**
|
133
|
+
* Each element in the library extends the `Base` class. It provides methods
|
134
|
+
* for selecting elements via HTML attributes along with other helpers.
|
135
|
+
*
|
136
|
+
* By default, `trigger`s and `content` will be selected via the `data-trigger` and
|
137
|
+
* `data-content` attributes. Alternatively, you can set the `trigger` or
|
138
|
+
* `content` attribute to a CSS selector to change the default selector from
|
139
|
+
* `[data-trigger]` or `[data-content]` to a selector of your choosing.
|
140
|
+
* This can be useful if you have multiple elements within one another.
|
141
|
+
*
|
142
|
+
* Each element can have multiple `trigger`s, but will only have one `content`.
|
143
|
+
*/
|
144
|
+
export class Base extends Trigger(Content(Lifecycle(Announce(HTMLElement)))) {
|
145
|
+
constructor() {
|
146
|
+
super();
|
147
|
+
}
|
132
148
|
}
|
package/dist/copy/index.d.ts
CHANGED
@@ -3,8 +3,15 @@ export type CopyAttributes = BaseAttributes & {
|
|
3
3
|
value: string;
|
4
4
|
};
|
5
5
|
/**
|
6
|
-
* Uses the
|
6
|
+
* Uses the
|
7
|
+
* [Clipboard API](https://developer.mozilla.org/en-US/docs/Web/API/Clipboard/writeText)
|
7
8
|
* to copy text.
|
9
|
+
*
|
10
|
+
* ### Attributes
|
11
|
+
*
|
12
|
+
* `value`
|
13
|
+
*
|
14
|
+
* Text to copy.
|
8
15
|
*/
|
9
16
|
export declare class Copy extends Base {
|
10
17
|
constructor();
|
package/dist/copy/index.js
CHANGED
@@ -1,7 +1,14 @@
|
|
1
1
|
import { Base } from "../base/index.js";
|
2
2
|
/**
|
3
|
-
* Uses the
|
3
|
+
* Uses the
|
4
|
+
* [Clipboard API](https://developer.mozilla.org/en-US/docs/Web/API/Clipboard/writeText)
|
4
5
|
* to copy text.
|
6
|
+
*
|
7
|
+
* ### Attributes
|
8
|
+
*
|
9
|
+
* `value`
|
10
|
+
*
|
11
|
+
* Text to copy.
|
5
12
|
*/
|
6
13
|
export class Copy extends Base {
|
7
14
|
constructor() {
|
package/dist/dialog/index.d.ts
CHANGED
@@ -11,6 +11,8 @@ export type DialogAttributes = BaseAttributes & {
|
|
11
11
|
* By default, the `HTMLDialogElement` doesn't close if the user clicks outside of it.
|
12
12
|
* Add a `click-outside-close` attribute to close when the user clicks outside.
|
13
13
|
*
|
14
|
+
* ### Attributes
|
15
|
+
*
|
14
16
|
* `remove-body-scroll`
|
15
17
|
*
|
16
18
|
* Add the `remove-body-scroll` attribute to remove the scroll from `document.body` when the dialog
|
package/dist/dialog/index.js
CHANGED
@@ -7,6 +7,8 @@ import { Base } from "../base/index.js";
|
|
7
7
|
* By default, the `HTMLDialogElement` doesn't close if the user clicks outside of it.
|
8
8
|
* Add a `click-outside-close` attribute to close when the user clicks outside.
|
9
9
|
*
|
10
|
+
* ### Attributes
|
11
|
+
*
|
10
12
|
* `remove-body-scroll`
|
11
13
|
*
|
12
14
|
* Add the `remove-body-scroll` attribute to remove the scroll from `document.body` when the dialog
|
package/dist/editor/index.d.ts
CHANGED
@@ -19,6 +19,23 @@ export type ContentElement = {
|
|
19
19
|
/**
|
20
20
|
* Enhances the `textarea` element with controls to add content and keyboard shortcuts. Compared to other WYSIWYG editors, the `text` value is just a `string`, so you can easily store it in a database or manipulate it without learning a separate API.
|
21
21
|
*
|
22
|
+
* - Automatically adds closing characters for `keyPairs`. For example, when
|
23
|
+
* typing `(`, `)` will be inserted and typed over when reached. All content
|
24
|
+
* with `data-type="wrap"` is also added to `keyPairs`.
|
25
|
+
* - Highlights the first word of the text inserted if it contains letters.
|
26
|
+
* - Automatically increments/decrements ordered lists.
|
27
|
+
* - Adds the starting character to the next line for `block` content.
|
28
|
+
* - On double click, highlight is corrected to only highlight the current word
|
29
|
+
* without space around it.
|
30
|
+
* - `tab` key will indent or dedent (+shift) instead of focus change if the
|
31
|
+
* selection is within a code block (three backticks).
|
32
|
+
* - When text is highlighted and a `wrap` character `keyPair` is typed, the
|
33
|
+
* highlighted text will be wrapped with the character instead of removing it.
|
34
|
+
* For example, if a word is highlighted and the `"` character is typed, the
|
35
|
+
* work will be surrounded by `"`s.
|
36
|
+
*
|
37
|
+
* ### Trigger attributes
|
38
|
+
*
|
22
39
|
* `data-value`
|
23
40
|
*
|
24
41
|
* Set the value of the text to be inserted using the `data-value` attribute on the `trigger`.
|
@@ -35,15 +52,6 @@ export type ContentElement = {
|
|
35
52
|
*
|
36
53
|
* Add a `ctrl`/`meta` keyboard shortcut for the content based on the `data-key` attribute.
|
37
54
|
*
|
38
|
-
* Other features:
|
39
|
-
*
|
40
|
-
* - Automatically adds closing characters for `keyPairs`. For example, when typing `(`, `)` will be inserted and typed over when reached. All content with `data-type="wrap"` is also added to `keyPairs`.
|
41
|
-
* - Highlights the first word of the text inserted if it contains letters.
|
42
|
-
* - Automatically increments/decrements ordered lists.
|
43
|
-
* - Adds the starting character to the next line for `block` content.
|
44
|
-
* - On double click, highlight is corrected to only highlight the current word without space around it.
|
45
|
-
* - `tab` key will indent or dedent (+shift) instead of focus change if the selection is within a code block (three backticks).
|
46
|
-
* - When text is highlighted and a `wrap` character `keyPair` is typed, the highlighted text will be wrapped with the character instead of removing it. For example, if a word is highlighted and the `"` character is typed, the work will be surrounded by `"`s.
|
47
55
|
*/
|
48
56
|
export declare class Editor extends Base {
|
49
57
|
#private;
|
package/dist/editor/index.js
CHANGED
@@ -2,6 +2,23 @@ import { Base } from "../base/index.js";
|
|
2
2
|
/**
|
3
3
|
* Enhances the `textarea` element with controls to add content and keyboard shortcuts. Compared to other WYSIWYG editors, the `text` value is just a `string`, so you can easily store it in a database or manipulate it without learning a separate API.
|
4
4
|
*
|
5
|
+
* - Automatically adds closing characters for `keyPairs`. For example, when
|
6
|
+
* typing `(`, `)` will be inserted and typed over when reached. All content
|
7
|
+
* with `data-type="wrap"` is also added to `keyPairs`.
|
8
|
+
* - Highlights the first word of the text inserted if it contains letters.
|
9
|
+
* - Automatically increments/decrements ordered lists.
|
10
|
+
* - Adds the starting character to the next line for `block` content.
|
11
|
+
* - On double click, highlight is corrected to only highlight the current word
|
12
|
+
* without space around it.
|
13
|
+
* - `tab` key will indent or dedent (+shift) instead of focus change if the
|
14
|
+
* selection is within a code block (three backticks).
|
15
|
+
* - When text is highlighted and a `wrap` character `keyPair` is typed, the
|
16
|
+
* highlighted text will be wrapped with the character instead of removing it.
|
17
|
+
* For example, if a word is highlighted and the `"` character is typed, the
|
18
|
+
* work will be surrounded by `"`s.
|
19
|
+
*
|
20
|
+
* ### Trigger attributes
|
21
|
+
*
|
5
22
|
* `data-value`
|
6
23
|
*
|
7
24
|
* Set the value of the text to be inserted using the `data-value` attribute on the `trigger`.
|
@@ -18,15 +35,6 @@ import { Base } from "../base/index.js";
|
|
18
35
|
*
|
19
36
|
* Add a `ctrl`/`meta` keyboard shortcut for the content based on the `data-key` attribute.
|
20
37
|
*
|
21
|
-
* Other features:
|
22
|
-
*
|
23
|
-
* - Automatically adds closing characters for `keyPairs`. For example, when typing `(`, `)` will be inserted and typed over when reached. All content with `data-type="wrap"` is also added to `keyPairs`.
|
24
|
-
* - Highlights the first word of the text inserted if it contains letters.
|
25
|
-
* - Automatically increments/decrements ordered lists.
|
26
|
-
* - Adds the starting character to the next line for `block` content.
|
27
|
-
* - On double click, highlight is corrected to only highlight the current word without space around it.
|
28
|
-
* - `tab` key will indent or dedent (+shift) instead of focus change if the selection is within a code block (three backticks).
|
29
|
-
* - When text is highlighted and a `wrap` character `keyPair` is typed, the highlighted text will be wrapped with the character instead of removing it. For example, if a word is highlighted and the `"` character is typed, the work will be surrounded by `"`s.
|
30
38
|
*/
|
31
39
|
export class Editor extends Base {
|
32
40
|
/** Array of `keyPair` characters that have been opened. */
|
package/dist/index.d.ts
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
export * from "./announcer/index.js";
|
2
|
-
export
|
2
|
+
export { Base, type BaseAttributes } from "./base/index.js";
|
3
3
|
export * from "./contextmenu/index.js";
|
4
4
|
export * from "./copy/index.js";
|
5
5
|
export * from "./dialog/index.js";
|
@@ -9,5 +9,6 @@ export * from "./intersect/index.js";
|
|
9
9
|
export * from "./prefetch/index.js";
|
10
10
|
export * from "./share/index.js";
|
11
11
|
export * from "./tablesort/index.js";
|
12
|
+
export * from "./tabs/index.js";
|
12
13
|
export * from "./wakelock/index.js";
|
13
14
|
export * from "./youtube/index.js";
|
package/dist/index.js
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
export * from "./announcer/index.js";
|
2
|
-
export
|
2
|
+
export { Base } from "./base/index.js";
|
3
3
|
export * from "./contextmenu/index.js";
|
4
4
|
export * from "./copy/index.js";
|
5
5
|
export * from "./dialog/index.js";
|
@@ -9,5 +9,6 @@ export * from "./intersect/index.js";
|
|
9
9
|
export * from "./prefetch/index.js";
|
10
10
|
export * from "./share/index.js";
|
11
11
|
export * from "./tablesort/index.js";
|
12
|
+
export * from "./tabs/index.js";
|
12
13
|
export * from "./wakelock/index.js";
|
13
14
|
export * from "./youtube/index.js";
|
@@ -8,6 +8,8 @@ type IntersectCallback = () => any;
|
|
8
8
|
*
|
9
9
|
* Use `onIntersect` and `onExit` to customize further with JavaScript.
|
10
10
|
*
|
11
|
+
* ### Attributes
|
12
|
+
*
|
11
13
|
* `threshold`
|
12
14
|
*
|
13
15
|
* Specify a `threshold` between `0` and `1` to determine how much of the `trigger` should be visible for the intersection to occur.
|
package/dist/intersect/index.js
CHANGED
@@ -4,6 +4,8 @@ import { Base } from "../base/index.js";
|
|
4
4
|
*
|
5
5
|
* Use `onIntersect` and `onExit` to customize further with JavaScript.
|
6
6
|
*
|
7
|
+
* ### Attributes
|
8
|
+
*
|
7
9
|
* `threshold`
|
8
10
|
*
|
9
11
|
* Specify a `threshold` between `0` and `1` to determine how much of the `trigger` should be visible for the intersection to occur.
|
package/dist/prefetch/index.d.ts
CHANGED
@@ -10,6 +10,8 @@ export type PrefetchAttributes = BaseAttributes & {
|
|
10
10
|
*
|
11
11
|
* If you are using a framework that already has a prefetch feature or uses a client side router, it is best to use the framework's feature instead of this element to ensure prefetching is working in accordance with the router.
|
12
12
|
*
|
13
|
+
* ### Attributes
|
14
|
+
*
|
13
15
|
* `strategy`
|
14
16
|
*
|
15
17
|
* Set the `strategy` attribute to specify the when the prefetch will take place.
|
package/dist/prefetch/index.js
CHANGED
@@ -4,6 +4,8 @@ import { Base } from "../base/index.js";
|
|
4
4
|
*
|
5
5
|
* If you are using a framework that already has a prefetch feature or uses a client side router, it is best to use the framework's feature instead of this element to ensure prefetching is working in accordance with the router.
|
6
6
|
*
|
7
|
+
* ### Attributes
|
8
|
+
*
|
7
9
|
* `strategy`
|
8
10
|
*
|
9
11
|
* Set the `strategy` attribute to specify the when the prefetch will take place.
|
@@ -88,12 +90,12 @@ export class Prefetch extends Base {
|
|
88
90
|
* @param options Prefetch options.
|
89
91
|
*/
|
90
92
|
prefetch(options = {
|
91
|
-
anchors: this.getTrigger(),
|
93
|
+
anchors: this.getTrigger(HTMLAnchorElement),
|
92
94
|
prerender: this.#prerender,
|
93
95
|
strategy: this.#strategy,
|
94
96
|
}) {
|
95
97
|
// defaults if partially defined
|
96
|
-
const { anchors = this.getTrigger(), prerender = this.#prerender, strategy = this.#strategy, } = options;
|
98
|
+
const { anchors = this.getTrigger(HTMLAnchorElement), prerender = this.#prerender, strategy = this.#strategy, } = options;
|
97
99
|
let prefetchTimer;
|
98
100
|
/**
|
99
101
|
* @param delay ms delay - for `hover`
|
package/dist/share/index.d.ts
CHANGED
@@ -1,8 +1,15 @@
|
|
1
1
|
import { Copy, type CopyAttributes } from "../copy/index.js";
|
2
2
|
export type ShareAttributes = CopyAttributes;
|
3
3
|
/**
|
4
|
-
* Uses the
|
4
|
+
* Uses the
|
5
|
+
* [Navigator API](https://developer.mozilla.org/en-US/docs/Web/API/Navigator/share)
|
5
6
|
* to share a url. If `share` is not supported, falls back to copy the text instead.
|
7
|
+
*
|
8
|
+
* ### Attributes
|
9
|
+
*
|
10
|
+
* `value`
|
11
|
+
*
|
12
|
+
* Text to share.
|
6
13
|
*/
|
7
14
|
export declare class Share extends Copy {
|
8
15
|
constructor();
|
package/dist/share/index.js
CHANGED
@@ -1,7 +1,14 @@
|
|
1
1
|
import { Copy } from "../copy/index.js";
|
2
2
|
/**
|
3
|
-
* Uses the
|
3
|
+
* Uses the
|
4
|
+
* [Navigator API](https://developer.mozilla.org/en-US/docs/Web/API/Navigator/share)
|
4
5
|
* to share a url. If `share` is not supported, falls back to copy the text instead.
|
6
|
+
*
|
7
|
+
* ### Attributes
|
8
|
+
*
|
9
|
+
* `value`
|
10
|
+
*
|
11
|
+
* Text to share.
|
5
12
|
*/
|
6
13
|
export class Share extends Copy {
|
7
14
|
constructor() {
|
package/dist/tablesort/index.js
CHANGED
@@ -16,6 +16,9 @@ export class TableSort extends Base {
|
|
16
16
|
constructor() {
|
17
17
|
super();
|
18
18
|
}
|
19
|
+
get #th() {
|
20
|
+
return this.getTrigger(HTMLTableCellElement);
|
21
|
+
}
|
19
22
|
/**
|
20
23
|
* Removes `data-asc` or `data-desc` from other triggers then sets the correct attribute on the selected trigger.
|
21
24
|
*
|
@@ -25,7 +28,7 @@ export class TableSort extends Base {
|
|
25
28
|
#setAttributes(trigger) {
|
26
29
|
const asc = "data-asc";
|
27
30
|
const desc = "data-desc";
|
28
|
-
for (const t of this.getTrigger()) {
|
31
|
+
for (const t of this.getTrigger(HTMLTableCellElement)) {
|
29
32
|
if (t !== trigger) {
|
30
33
|
t.removeAttribute(asc);
|
31
34
|
t.removeAttribute(desc);
|
@@ -42,7 +45,7 @@ export class TableSort extends Base {
|
|
42
45
|
}
|
43
46
|
mount() {
|
44
47
|
const tbody = this.getContent(HTMLTableSectionElement);
|
45
|
-
for (const trigger of this
|
48
|
+
for (const trigger of this.#th) {
|
46
49
|
trigger.tabIndex = 0;
|
47
50
|
trigger.role = "button";
|
48
51
|
const listener = () => {
|
@@ -0,0 +1 @@
|
|
1
|
+
export {};
|
@@ -0,0 +1,37 @@
|
|
1
|
+
import { Base, type BaseAttributes } from "../base/index.js";
|
2
|
+
export type TabAttributes = BaseAttributes & {
|
3
|
+
orientation?: "horizontal" | "vertical";
|
4
|
+
};
|
5
|
+
/**
|
6
|
+
* Progressively enhance a list of links and content to be tabs by
|
7
|
+
* wrapping with the `Tabs` element. Each `trigger` should be an
|
8
|
+
* `HTMLAnchorElement` with the `href` attribute set to the `id` of the
|
9
|
+
* corresponding tab panel.
|
10
|
+
*
|
11
|
+
* > Tip: Set the `height` of the element the `panel`s are contained in with
|
12
|
+
* > CSS to prevent layout shift when JS is loaded.
|
13
|
+
*
|
14
|
+
* This element is based on
|
15
|
+
* [Chris Ferdinandi's Toggle Tabs](https://gomakethings.com/a-web-component-ui-library-for-people-who-love-html/#toggle-tabs)
|
16
|
+
* design.
|
17
|
+
*
|
18
|
+
* [ARIA Reference](https://www.w3.org/WAI/ARIA/apg/patterns/tabs/)
|
19
|
+
*
|
20
|
+
* - Sets the correct ARIA attributes on each element.
|
21
|
+
* - Supports keyboard navigation based on the `orientation` attribute.
|
22
|
+
* - First tab is selected by default if no `aria-selected="true"` attribute is
|
23
|
+
* found on another tab.
|
24
|
+
* - `tablist` is calculated based on the deepest common parent of the tabs,
|
25
|
+
* throws an error if not found.
|
26
|
+
*
|
27
|
+
* ### Attributes
|
28
|
+
*
|
29
|
+
* `orientation`
|
30
|
+
*
|
31
|
+
* Set `orientation="vertical"` if the `tablist` element is displayed vertically.
|
32
|
+
*/
|
33
|
+
export declare class Tabs extends Base {
|
34
|
+
#private;
|
35
|
+
constructor();
|
36
|
+
mount(): void;
|
37
|
+
}
|
@@ -0,0 +1,147 @@
|
|
1
|
+
import { Base } from "../base/index.js";
|
2
|
+
/**
|
3
|
+
* Progressively enhance a list of links and content to be tabs by
|
4
|
+
* wrapping with the `Tabs` element. Each `trigger` should be an
|
5
|
+
* `HTMLAnchorElement` with the `href` attribute set to the `id` of the
|
6
|
+
* corresponding tab panel.
|
7
|
+
*
|
8
|
+
* > Tip: Set the `height` of the element the `panel`s are contained in with
|
9
|
+
* > CSS to prevent layout shift when JS is loaded.
|
10
|
+
*
|
11
|
+
* This element is based on
|
12
|
+
* [Chris Ferdinandi's Toggle Tabs](https://gomakethings.com/a-web-component-ui-library-for-people-who-love-html/#toggle-tabs)
|
13
|
+
* design.
|
14
|
+
*
|
15
|
+
* [ARIA Reference](https://www.w3.org/WAI/ARIA/apg/patterns/tabs/)
|
16
|
+
*
|
17
|
+
* - Sets the correct ARIA attributes on each element.
|
18
|
+
* - Supports keyboard navigation based on the `orientation` attribute.
|
19
|
+
* - First tab is selected by default if no `aria-selected="true"` attribute is
|
20
|
+
* found on another tab.
|
21
|
+
* - `tablist` is calculated based on the deepest common parent of the tabs,
|
22
|
+
* throws an error if not found.
|
23
|
+
*
|
24
|
+
* ### Attributes
|
25
|
+
*
|
26
|
+
* `orientation`
|
27
|
+
*
|
28
|
+
* Set `orientation="vertical"` if the `tablist` element is displayed vertically.
|
29
|
+
*/
|
30
|
+
export class Tabs extends Base {
|
31
|
+
/** Supported keys for keyboard navigation. */
|
32
|
+
#keys = ["ArrowRight", "ArrowDown", "ArrowLeft", "ArrowUp", "Home", "End"];
|
33
|
+
/** Currently selected tab. */
|
34
|
+
#selected = { index: 0 };
|
35
|
+
constructor() {
|
36
|
+
super();
|
37
|
+
}
|
38
|
+
/** User provided orientation of the `tablist`. */
|
39
|
+
get #orientation() {
|
40
|
+
return this.getAttribute("orientation") ?? "horizontal";
|
41
|
+
}
|
42
|
+
get #tabs() {
|
43
|
+
return this.getTrigger(HTMLAnchorElement);
|
44
|
+
}
|
45
|
+
/**
|
46
|
+
* @param tab
|
47
|
+
* @returns The ancestors of the tab up to `this`.
|
48
|
+
*/
|
49
|
+
#ancestors(tab) {
|
50
|
+
const ancestors = new Set();
|
51
|
+
let current = tab;
|
52
|
+
while ((current = current?.parentElement) && current !== this) {
|
53
|
+
ancestors.add(current);
|
54
|
+
}
|
55
|
+
return ancestors;
|
56
|
+
}
|
57
|
+
/** A map of each `tab` and its corresponding `panel`. */
|
58
|
+
get #map() {
|
59
|
+
const map = new Map();
|
60
|
+
for (const tab of this.#tabs) {
|
61
|
+
const panel = this.querySelector(tab.hash);
|
62
|
+
if (!(panel instanceof HTMLElement))
|
63
|
+
throw new Error(`Tabs: No HTMLElement with ID of ${tab.hash} found.`);
|
64
|
+
map.set(tab, panel);
|
65
|
+
}
|
66
|
+
return map;
|
67
|
+
}
|
68
|
+
mount() {
|
69
|
+
// create tablist
|
70
|
+
const [first, ...rest] = this.#tabs;
|
71
|
+
let common = this.#ancestors(first);
|
72
|
+
for (let i = 0; i < rest.length; i++) {
|
73
|
+
common = common.intersection(this.#ancestors(rest[i]));
|
74
|
+
}
|
75
|
+
const [tablist] = common;
|
76
|
+
if (!tablist)
|
77
|
+
throw new Error("Tabs: No common parent element found for triggers.");
|
78
|
+
tablist.role = "tablist";
|
79
|
+
tablist.ariaOrientation = this.#orientation;
|
80
|
+
// enhance tabs/panels
|
81
|
+
let index = 0;
|
82
|
+
for (const [tab, panel] of this.#map) {
|
83
|
+
tab.role = "tab";
|
84
|
+
tab.id = `tab-${panel.id}`;
|
85
|
+
tab.setAttribute("aria-controls", panel.id);
|
86
|
+
if (tab.ariaSelected)
|
87
|
+
this.#selected = { tab, index };
|
88
|
+
panel.role = "tabpanel";
|
89
|
+
panel.setAttribute("aria-labelledby", tab.id);
|
90
|
+
tab.addEventListener(this.event, (e) => {
|
91
|
+
e.preventDefault();
|
92
|
+
for (const [t, p] of this.#map) {
|
93
|
+
if (t === tab) {
|
94
|
+
// show current
|
95
|
+
t.ariaSelected = "true";
|
96
|
+
t.tabIndex = 0;
|
97
|
+
p.hidden = false;
|
98
|
+
}
|
99
|
+
else {
|
100
|
+
// hide others
|
101
|
+
t.ariaSelected = "false";
|
102
|
+
t.tabIndex = -1;
|
103
|
+
p.hidden = true;
|
104
|
+
}
|
105
|
+
}
|
106
|
+
});
|
107
|
+
index++;
|
108
|
+
}
|
109
|
+
// fallback to first
|
110
|
+
if (!this.#selected.tab)
|
111
|
+
this.#selected.tab = this.#tabs[0];
|
112
|
+
// select the current tab
|
113
|
+
this.#selected.tab?.click();
|
114
|
+
// handle keyboard navigation
|
115
|
+
this.addEventListener("keydown", (e) => {
|
116
|
+
const i = this.#keys.indexOf(e.key);
|
117
|
+
if (i === -1)
|
118
|
+
return;
|
119
|
+
const previousIndex = this.#selected.index;
|
120
|
+
const vertical = this.#orientation === "vertical";
|
121
|
+
if (((!vertical && i === 0) || (vertical && i === 1)) &&
|
122
|
+
this.#tabs[this.#selected.index + 1]) {
|
123
|
+
// next
|
124
|
+
this.#selected.tab = this.#tabs[++this.#selected.index];
|
125
|
+
}
|
126
|
+
else if (((!vertical && i === 2) || (vertical && i === 3)) &&
|
127
|
+
this.#tabs[this.#selected.index - 1]) {
|
128
|
+
// previous
|
129
|
+
this.#selected.tab = this.#tabs[--this.#selected.index];
|
130
|
+
}
|
131
|
+
else if (i === 4) {
|
132
|
+
// home
|
133
|
+
this.#selected = { tab: this.#tabs[0], index: 0 };
|
134
|
+
}
|
135
|
+
else if (i === 5) {
|
136
|
+
// end
|
137
|
+
const index = this.#tabs.length - 1;
|
138
|
+
this.#selected = { tab: this.#tabs[index], index };
|
139
|
+
}
|
140
|
+
if (this.#selected.index === previousIndex)
|
141
|
+
return;
|
142
|
+
e.preventDefault();
|
143
|
+
this.#selected.tab?.click();
|
144
|
+
this.#selected.tab?.focus();
|
145
|
+
});
|
146
|
+
}
|
147
|
+
}
|
@@ -0,0 +1,7 @@
|
|
1
|
+
import type { Constructor } from "../base/index.js";
|
2
|
+
/**
|
3
|
+
* @param actual Element to validate.
|
4
|
+
* @param expected Constructor of the expected element.
|
5
|
+
* @returns If valid returns `actual` otherwise throws `TypeError`.
|
6
|
+
*/
|
7
|
+
export declare const validate: <T extends HTMLElement>(actual: unknown, expected: Constructor<T>) => T;
|
@@ -0,0 +1,10 @@
|
|
1
|
+
/**
|
2
|
+
* @param actual Element to validate.
|
3
|
+
* @param expected Constructor of the expected element.
|
4
|
+
* @returns If valid returns `actual` otherwise throws `TypeError`.
|
5
|
+
*/
|
6
|
+
export const validate = (actual, expected) => {
|
7
|
+
if (!(actual instanceof expected))
|
8
|
+
throw new TypeError(`${actual} is not an instance of ${expected.name}.`);
|
9
|
+
return actual;
|
10
|
+
};
|