drab 6.3.0 → 6.4.1

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.
@@ -1,26 +1,8 @@
1
1
  import { Announcer } from "../announcer/index.js";
2
- /**
3
- * Each element in the library extends the `Base` class. It provides methods
4
- * for selecting elements via HTML attributes along with other helpers.
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,25 +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
- * @param message message to announce to screen readers
40
- */
41
- announce(message) {
42
- Base.#announcer.announce(message);
43
- }
44
20
  getTrigger(instance = HTMLElement) {
45
21
  const triggers = this.querySelectorAll(this.getAttribute("trigger") ?? "[data-trigger]");
46
22
  for (const trigger of triggers)
47
- this.#validate(trigger, instance);
23
+ validate(trigger, instance);
48
24
  return triggers;
49
25
  }
26
+ /**
27
+ * @param listener Listener to attach to all of the `trigger` elements.
28
+ */
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);
38
+ }
50
39
  getContent(instance = HTMLElement) {
51
- return this.#validate(this.querySelector(this.getAttribute("content") ?? "[data-content]"), instance);
40
+ return validate(this.querySelector(this.getAttribute("content") ?? "[data-content]"), instance);
52
41
  }
53
42
  /**
54
43
  * Finds the `HTMLElement | HTMLTemplateElement` via the `swap` selector and
@@ -88,22 +77,26 @@ export class Base extends HTMLElement {
88
77
  }
89
78
  }
90
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
+ }
91
87
  safeListener(type, listener, target = document.body, options = {}) {
92
88
  options.signal = this.#listenerController.signal;
93
89
  target.addEventListener(type, listener, options);
94
90
  }
95
91
  /**
96
- * @param listener Listener to attach to all of the `trigger` elements.
97
- */
98
- triggerListener(listener, type = this.event, options) {
99
- for (const trigger of this.getTrigger()) {
100
- trigger.addEventListener(type, listener, options);
101
- }
102
- }
103
- /**
104
- * 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`.
105
94
  *
106
- * The reason for this is to make these elements work better with frameworks like Svelte. For SSR this isn't necessary, but when client side rendering, the HTML within the custom element isn't available before `connectedCallback` is called. By waiting until the next microtask, the HTML content is available---then for example, listeners can be attached to elements inside.
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.
107
100
  */
108
101
  mount() { }
109
102
  /** Called when custom element is added to the page. */
@@ -119,14 +112,37 @@ export class Base extends HTMLElement {
119
112
  this.destroy();
120
113
  this.#listenerController.abort();
121
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
+ }
122
125
  /**
123
- * @param actual Element to validate.
124
- * @param expected Constructor of the expected element.
125
- * @returns If valid returns `actual` otherwise throws `TypeError`.
126
+ * @param message message to announce to screen readers
126
127
  */
127
- #validate(actual, expected) {
128
- if (!(actual instanceof expected))
129
- throw new TypeError(`${actual} is not an instance of ${expected.name}.`);
130
- return actual;
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();
131
147
  }
132
148
  }
@@ -29,7 +29,7 @@ export class Copy extends Base {
29
29
  * @param value The `value` to copy
30
30
  */
31
31
  copy(value = this.value) {
32
- this.announce(`copied ${value} to clipboard`);
32
+ this.announce("copied text to clipboard");
33
33
  this.swapContent();
34
34
  return navigator.clipboard.writeText(value);
35
35
  }
@@ -23,9 +23,9 @@ export declare class Dialog extends Base {
23
23
  constructor();
24
24
  /** The `HTMLDialogElement` within the element. */
25
25
  get dialog(): HTMLDialogElement;
26
- /** `HTMLDialogElement.showModal()` with animation. */
26
+ /** Wraps `HTMLDialogElement.showModal()`. */
27
27
  show(): void;
28
- /** `HTMLDialogElement.close()` with animation. */
28
+ /** Wraps `HTMLDialogElement.close()`. */
29
29
  close(): void;
30
30
  /** `show` or `close` depending on the dialog's `open` attribute. */
31
31
  toggle(): void;
@@ -15,7 +15,6 @@ import { Base } from "../base/index.js";
15
15
  * is open.
16
16
  */
17
17
  export class Dialog extends Base {
18
- /** The initial margin-right value of the body element. */
19
18
  #initialBodyMarginRight = parseInt(getComputedStyle(document.body).marginRight);
20
19
  constructor() {
21
20
  super();
@@ -36,12 +35,12 @@ export class Dialog extends Base {
36
35
  document.body.style.overflow = show ? "hidden" : "";
37
36
  }
38
37
  }
39
- /** `HTMLDialogElement.showModal()` with animation. */
38
+ /** Wraps `HTMLDialogElement.showModal()`. */
40
39
  show() {
41
40
  this.dialog.showModal();
42
41
  this.#toggleBodyScroll(true);
43
42
  }
44
- /** `HTMLDialogElement.close()` with animation. */
43
+ /** Wraps `HTMLDialogElement.close()`. */
45
44
  close() {
46
45
  this.#toggleBodyScroll(false);
47
46
  this.dialog.close();
@@ -57,7 +56,6 @@ export class Dialog extends Base {
57
56
  this.triggerListener(() => this.toggle());
58
57
  this.safeListener("keydown", (e) => {
59
58
  if (e.key === "Escape" && this.dialog.open) {
60
- // to execute animation
61
59
  e.preventDefault();
62
60
  this.close();
63
61
  }
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  export * from "./announcer/index.js";
2
- export * from "./base/index.js";
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";
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  export * from "./announcer/index.js";
2
- export * from "./base/index.js";
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";
@@ -8,7 +8,7 @@ export type TabAttributes = BaseAttributes & {
8
8
  * `HTMLAnchorElement` with the `href` attribute set to the `id` of the
9
9
  * corresponding tab panel.
10
10
  *
11
- * > Tip: Set the `height` of the element the panels are contained in with
11
+ * > Tip: Set the `height` of the element the `panel`s are contained in with
12
12
  * > CSS to prevent layout shift when JS is loaded.
13
13
  *
14
14
  * This element is based on
@@ -5,7 +5,7 @@ import { Base } from "../base/index.js";
5
5
  * `HTMLAnchorElement` with the `href` attribute set to the `id` of the
6
6
  * corresponding tab panel.
7
7
  *
8
- * > Tip: Set the `height` of the element the panels are contained in with
8
+ * > Tip: Set the `height` of the element the `panel`s are contained in with
9
9
  * > CSS to prevent layout shift when JS is loaded.
10
10
  *
11
11
  * This element is based on
@@ -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
+ };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "drab",
3
3
  "description": "Interactivity for You",
4
- "version": "6.3.0",
4
+ "version": "6.4.1",
5
5
  "homepage": "https://drab.robino.dev",
6
6
  "license": "MIT",
7
7
  "author": {