drab 2.1.0 → 2.2.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.
@@ -0,0 +1,164 @@
1
+ <!--
2
+ @component
3
+
4
+ ### Accordion
5
+
6
+ Displays a list of `details` elements.
7
+
8
+ @props
9
+
10
+ - `autoClose` - if true, other items close when a new one is opened
11
+ - `classDetails` - class of the `div` around each `details` element
12
+ - `classIcon` - class of the `div` around the icon
13
+ - `classSlot` - class of all the `slot` elements
14
+ - `classSummary` - class of all the `summary` elements
15
+ - `class`
16
+ - `content` - array of `AccordionContent` elements
17
+ - `icon`
18
+ - `id`
19
+ - `transition` - rotates the icon, slides the content, defaults to empty object, set to false to remove
20
+
21
+ @slots
22
+
23
+ Pass components into the `content` prop if needed. `AccordionContent` has `summary` and `slot` attributes of type `string | ComponentType`.
24
+
25
+ @example
26
+
27
+ ```svelte
28
+ <script>
29
+ import { Accordion } from "drab";
30
+ </script>
31
+
32
+ <Accordion
33
+ content={[
34
+ { summary: "Is it accessible?", slot: "Yes." },
35
+ {
36
+ summary: "Is it styled?",
37
+ slot: "Nope, style with global styles.",
38
+ },
39
+ {
40
+ summary: "Is it animated?",
41
+ slot: "Yes, with the transition prop.",
42
+ },
43
+ { summary: "Does it work without Javascript?", slot: "Yes." },
44
+ ]}
45
+ />
46
+ ```
47
+ -->
48
+
49
+ <script context="module"></script>
50
+
51
+ <script>import { onMount } from "svelte";
52
+ import { slide } from "svelte/transition";
53
+ import { prefersReducedMotion } from "../util/accessibility";
54
+ let className = "";
55
+ export { className as class };
56
+ export let id = "";
57
+ export let content;
58
+ export let icon = "";
59
+ export let classDetails = "";
60
+ export let classSummary = "";
61
+ export let classSlot = "";
62
+ export let classIcon = "";
63
+ export let transition = {};
64
+ export let autoClose = true;
65
+ let clientJs = false;
66
+ for (const item of content) {
67
+ if (!item.classContentDetails)
68
+ item.classContentDetails = "";
69
+ if (!item.classContentSlot)
70
+ item.classContentSlot = "";
71
+ if (!item.classContentSummary)
72
+ item.classContentSummary = "";
73
+ }
74
+ const toggleOpen = (i) => {
75
+ content[i].open = !content[i].open;
76
+ if (autoClose) {
77
+ for (let j = 0; j < content.length; j++) {
78
+ const item = content[j];
79
+ if (j !== i)
80
+ item.open = false;
81
+ }
82
+ }
83
+ };
84
+ onMount(() => {
85
+ clientJs = true;
86
+ if (prefersReducedMotion())
87
+ transition = false;
88
+ });
89
+ </script>
90
+
91
+ <div class="transition"></div>
92
+
93
+ <div class={className} {id}>
94
+ {#each content as { classContentDetails, summary, classContentSummary, slot, classContentSlot, open }, i}
95
+ <div class="{classDetails} {classContentDetails}">
96
+ <details bind:open>
97
+ <!-- svelte-ignore a11y-no-redundant-roles -->
98
+ <summary
99
+ role="button"
100
+ tabindex="0"
101
+ class={classSummary}
102
+ on:click={(e) => {
103
+ e.preventDefault();
104
+ toggleOpen(i);
105
+ }}
106
+ on:keydown={(e) => {
107
+ if (e.key === "Enter") {
108
+ e.preventDefault();
109
+ toggleOpen(i);
110
+ }
111
+ }}
112
+ >
113
+ {#if typeof summary !== "string"}
114
+ <svelte:component this={summary} class={classContentSummary} />
115
+ {:else}
116
+ <span class={classContentSummary}>{summary}</span>
117
+ {/if}
118
+ {#if icon}
119
+ <div
120
+ class={classIcon}
121
+ class:db-rotate-180={open}
122
+ class:db-transition={transition}
123
+ >
124
+ {#if typeof icon !== "string"}
125
+ <svelte:component this={icon} />
126
+ {:else}
127
+ <span>{icon}</span>
128
+ {/if}
129
+ </div>
130
+ {/if}
131
+ </summary>
132
+ {#if !clientJs || !transition}
133
+ <div class={classSlot}>
134
+ {#if typeof slot !== "string"}
135
+ <svelte:component this={slot} class={classContentSlot} />
136
+ {:else}
137
+ <div class={classContentSlot}>{slot}</div>
138
+ {/if}
139
+ </div>
140
+ {/if}
141
+ </details>
142
+ {#if clientJs && open && transition}
143
+ <div transition:slide={transition} class={classSlot}>
144
+ {#if typeof slot !== "string"}
145
+ <svelte:component this={slot} class={classContentSlot} />
146
+ {:else}
147
+ <div class={classContentSlot}>{slot}</div>
148
+ {/if}
149
+ </div>
150
+ {/if}
151
+ </div>
152
+ {/each}
153
+ </div>
154
+
155
+ <style>
156
+ .db-rotate-180 {
157
+ transform: rotate(180deg);
158
+ }
159
+ .db-transition {
160
+ transition-property: transform;
161
+ transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
162
+ transition-duration: 150ms;
163
+ }
164
+ </style>
@@ -0,0 +1,86 @@
1
+ import { SvelteComponent } from "svelte";
2
+ import type { ComponentType } from "svelte";
3
+ export interface AccordionContent {
4
+ /** `details` element class */
5
+ classContentDetails?: string;
6
+ /** content of the `summary` element */
7
+ summary: string | ComponentType;
8
+ /** `summary` element class */
9
+ classContentSummary?: string;
10
+ /** content of the `slot` */
11
+ slot: string | ComponentType;
12
+ /** `slot` element class */
13
+ classContentSlot?: string;
14
+ /** controls whether the slotted content is displayed */
15
+ open?: boolean;
16
+ }
17
+ import { type SlideParams } from "svelte/transition";
18
+ declare const __propDef: {
19
+ props: {
20
+ class?: string | undefined;
21
+ id?: string | undefined;
22
+ /** array of `AccordionContent` elements */ content: AccordionContent[];
23
+ icon?: string | ComponentType | undefined;
24
+ /** class of the `div` around each `details` element */ classDetails?: string | undefined;
25
+ /** class of all the `summary` elements */ classSummary?: string | undefined;
26
+ /** class of all the `slot` elements */ classSlot?: string | undefined;
27
+ /** class of the `div` around the icon */ classIcon?: string | undefined;
28
+ /** rotates the icon, slides the content, defaults to empty object, set to false to remove */ transition?: false | SlideParams | undefined;
29
+ /** if true, other items close when a new one is opened */ autoClose?: boolean | undefined;
30
+ };
31
+ events: {
32
+ [evt: string]: CustomEvent<any>;
33
+ };
34
+ slots: {};
35
+ };
36
+ export type AccordionProps = typeof __propDef.props;
37
+ export type AccordionEvents = typeof __propDef.events;
38
+ export type AccordionSlots = typeof __propDef.slots;
39
+ /**
40
+ * ### Accordion
41
+ *
42
+ * Displays a list of `details` elements.
43
+ *
44
+ * @props
45
+ *
46
+ * - `autoClose` - if true, other items close when a new one is opened
47
+ * - `classDetails` - class of the `div` around each `details` element
48
+ * - `classIcon` - class of the `div` around the icon
49
+ * - `classSlot` - class of all the `slot` elements
50
+ * - `classSummary` - class of all the `summary` elements
51
+ * - `class`
52
+ * - `content` - array of `AccordionContent` elements
53
+ * - `icon`
54
+ * - `id`
55
+ * - `transition` - rotates the icon, slides the content, defaults to empty object, set to false to remove
56
+ *
57
+ * @slots
58
+ *
59
+ * Pass components into the `content` prop if needed. `AccordionContent` has `summary` and `slot` attributes of type `string | ComponentType`.
60
+ *
61
+ * @example
62
+ *
63
+ * ```svelte
64
+ * <script>
65
+ * import { Accordion } from "drab";
66
+ * </script>
67
+ *
68
+ * <Accordion
69
+ * content={[
70
+ * { summary: "Is it accessible?", slot: "Yes." },
71
+ * {
72
+ * summary: "Is it styled?",
73
+ * slot: "Nope, style with global styles.",
74
+ * },
75
+ * {
76
+ * summary: "Is it animated?",
77
+ * slot: "Yes, with the transition prop.",
78
+ * },
79
+ * { summary: "Does it work without Javascript?", slot: "Yes." },
80
+ * ]}
81
+ * />
82
+ * ```
83
+ */
84
+ export default class Accordion extends SvelteComponent<AccordionProps, AccordionEvents, AccordionSlots> {
85
+ }
86
+ export {};
@@ -91,6 +91,7 @@ onMount(() => {
91
91
  style:opacity={display ? "100%" : "0%"}
92
92
  style:top="{coordinates.y}px"
93
93
  style:left="{coordinates.x}px"
94
+ inert={display ? false : true}
94
95
  >
95
96
  <slot>Context Menu</slot>
96
97
  </div>
@@ -1,4 +1,5 @@
1
1
  import { SvelteComponent } from "svelte";
2
+ import type { ComponentType } from "svelte";
2
3
  /**
3
4
  * - `EditorContentElement` to pass into the `contentElements` array prop
4
5
  * - `contentElements` prop creates a list of button controls to insert
@@ -18,7 +19,6 @@ export interface EditorContentElement {
18
19
  /** class to apply to the specific button */
19
20
  class?: string;
20
21
  }
21
- import { type ComponentType } from "svelte";
22
22
  declare const __propDef: {
23
23
  props: {
24
24
  /** an array of content elements for the controls */ contentElements?: EditorContentElement[] | undefined;
@@ -51,7 +51,7 @@ export let title = "Fullscreen";
51
51
  export let targetElement = null;
52
52
  export let confirmMessage = "";
53
53
  export let classNoscript = "";
54
- let clientJs = false;
54
+ let supported = false;
55
55
  let fullscreen = false;
56
56
  const onClick = () => {
57
57
  if (fullscreen) {
@@ -69,7 +69,8 @@ const onClick = () => {
69
69
  }
70
70
  };
71
71
  onMount(() => {
72
- clientJs = true;
72
+ if (document.documentElement.requestFullscreen)
73
+ supported = true;
73
74
  if (!targetElement)
74
75
  targetElement = document.documentElement;
75
76
  });
@@ -77,7 +78,7 @@ onMount(() => {
77
78
 
78
79
  <svelte:window on:fullscreenchange={() => (fullscreen = !fullscreen)} />
79
80
 
80
- <button disabled={!clientJs} on:click={onClick} class={className} {id} {title}>
81
+ <button disabled={!supported} on:click={onClick} class={className} {id} {title}>
81
82
  {#if fullscreen}
82
83
  <slot name="enabled">Exit Fullscreen</slot>
83
84
  {:else}
@@ -130,6 +130,7 @@ onMount(() => {
130
130
  class:db-type-hover={clientEventType === "hover"}
131
131
  style:top="{coordinates.y}px"
132
132
  style:left="{coordinates.x}px"
133
+ inert={clientEventType === "hover" || display ? false : true}
133
134
  >
134
135
  <slot>Popover</slot>
135
136
  </div>
package/dist/index.d.ts CHANGED
@@ -1,3 +1,5 @@
1
+ import Accordion from "./components/Accordion.svelte";
2
+ import type { AccordionContent } from "./components/Accordion.svelte";
1
3
  import Chord from "./components/Chord.svelte";
2
4
  import type { ChordNote } from "./components/Chord.svelte";
3
5
  import ContextMenu from "./components/ContextMenu.svelte";
@@ -10,4 +12,4 @@ import FullscreenButton from "./components/FullscreenButton.svelte";
10
12
  import Popover from "./components/Popover.svelte";
11
13
  import ShareButton from "./components/ShareButton.svelte";
12
14
  import YouTube from "./components/YouTube.svelte";
13
- export { Chord, type ChordNote, ContextMenu, CopyButton, DataTable, type DataTableRow, Editor, type EditorContentElement, FullscreenButton, Popover, ShareButton, YouTube, };
15
+ export { Accordion, type AccordionContent, Chord, type ChordNote, ContextMenu, CopyButton, DataTable, type DataTableRow, Editor, type EditorContentElement, FullscreenButton, Popover, ShareButton, YouTube, };
package/dist/index.js CHANGED
@@ -1,3 +1,4 @@
1
+ import Accordion from "./components/Accordion.svelte";
1
2
  import Chord from "./components/Chord.svelte";
2
3
  import ContextMenu from "./components/ContextMenu.svelte";
3
4
  import CopyButton from "./components/CopyButton.svelte";
@@ -7,4 +8,4 @@ import FullscreenButton from "./components/FullscreenButton.svelte";
7
8
  import Popover from "./components/Popover.svelte";
8
9
  import ShareButton from "./components/ShareButton.svelte";
9
10
  import YouTube from "./components/YouTube.svelte";
10
- export { Chord, ContextMenu, CopyButton, DataTable, Editor, FullscreenButton, Popover, ShareButton, YouTube, };
11
+ export { Accordion, Chord, ContextMenu, CopyButton, DataTable, Editor, FullscreenButton, Popover, ShareButton, YouTube, };
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Checks to see if the user prefers reduced motion
3
+ *
4
+ * @returns `true` if the user prefers reduced motion
5
+ */
6
+ export declare const prefersReducedMotion: () => boolean;
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Checks to see if the user prefers reduced motion
3
+ *
4
+ * @returns `true` if the user prefers reduced motion
5
+ */
6
+ export const prefersReducedMotion = () => {
7
+ const mediaQuery = window.matchMedia("(prefers-reduced-motion: reduce)");
8
+ if (mediaQuery.matches)
9
+ return true;
10
+ return false;
11
+ };
package/package.json CHANGED
@@ -1,11 +1,12 @@
1
1
  {
2
2
  "name": "drab",
3
- "version": "2.1.0",
3
+ "version": "2.2.0",
4
4
  "description": "An unstyled Svelte component library",
5
5
  "keywords": [
6
6
  "components",
7
7
  "Svelte",
8
8
  "SvelteKit",
9
+ "Accordion",
9
10
  "Chord",
10
11
  "ContextMenu",
11
12
  "Copy",
@@ -34,7 +35,7 @@
34
35
  "lint": "prettier --check . && eslint .",
35
36
  "format": "prettier --write . --plugin=prettier-plugin-svelte --plugin=prettier-plugin-tailwindcss",
36
37
  "pub": "npm publish --access public",
37
- "doc": "node src/lib/util/buildDocs.js"
38
+ "doc": "node scripts/buildDocs.js"
38
39
  },
39
40
  "exports": {
40
41
  ".": {
@@ -60,6 +61,7 @@
60
61
  "eslint": "^8.46.0",
61
62
  "eslint-config-prettier": "^9.0.0",
62
63
  "eslint-plugin-svelte": "^2.32.4",
64
+ "marked": "^7.0.2",
63
65
  "postcss": "^8.4.27",
64
66
  "prettier": "^3.0.1",
65
67
  "prettier-plugin-svelte": "^3.0.3",
@@ -1 +0,0 @@
1
- export {};
@@ -1,3 +0,0 @@
1
- import { documentPropsDir } from "./documentProps.js";
2
-
3
- documentPropsDir("./src/lib/components");
@@ -1,2 +0,0 @@
1
- export function documentPropsDir(dir: string): Promise<void>;
2
- export function documentProps(path: string): Promise<void>;
@@ -1,86 +0,0 @@
1
- import fs from "fs/promises";
2
- import path from "path";
3
-
4
- /**
5
- * Runs `documentProps` over a given directory
6
- *
7
- * @param {string} dir path to directory
8
- */
9
- export const documentPropsDir = async (dir) => {
10
- const files = await fs.readdir(dir);
11
- for (const file of files) {
12
- if (path.extname(file) === ".svelte") {
13
- documentProps(path.join(dir, file));
14
- }
15
- }
16
- };
17
-
18
- /**
19
- * - Finds "@props" in a component
20
- * - If found, overwrites the docs with the generated docs
21
- *
22
- * @param {string} path path to component
23
- */
24
- export const documentProps = async (path) => {
25
- const code = await fs.readFile(path, "utf-8");
26
- const lines = code.split("\n");
27
- const docs = getPropDocs(lines);
28
- for (let i = 0; i < lines.length; i++) {
29
- const line = lines[i].trim();
30
- if (line.startsWith("@props")) {
31
- for (let j = i + 1; j < lines.length; j++) {
32
- // for the following lines...
33
- const checkLine = lines[j].trim();
34
- if (
35
- checkLine.startsWith("@") ||
36
- checkLine.startsWith("-->") ||
37
- checkLine.startsWith("#") ||
38
- checkLine.startsWith("|")
39
- ) {
40
- // delete current / add new
41
- lines.splice(i + 1, j - i - 1, docs);
42
- // join back together
43
- const documented = lines.join("\n");
44
- await fs.writeFile(path, documented);
45
- console.log("Documented " + path);
46
- break;
47
- }
48
- }
49
- break;
50
- }
51
- if (i === lines.length - 1) {
52
- // last line
53
- console.log("No `@props` found for " + path);
54
- }
55
- }
56
- };
57
-
58
- /**
59
- * - Finds the props and JSDoc comments
60
- *
61
- * @param {string[]} lines lines of code
62
- */
63
- const getPropDocs = (lines) => {
64
- const docs = ["\n"];
65
- for (let i = 0; i < lines.length; i++) {
66
- const line = lines[i].trim();
67
- const isClass = line === "export { className as class };";
68
- if (line.startsWith("export let ") || isClass) {
69
- let propName = line.split(" ")[2];
70
- if (isClass) {
71
- propName = "class";
72
- }
73
- if (propName.endsWith(":")) propName = propName.slice(0, -1);
74
- const previousLine = lines[i - 1].trim();
75
- let jsDoc = "";
76
- if (previousLine.endsWith("*/")) {
77
- if (previousLine.startsWith("/**")) {
78
- jsDoc = "- " + previousLine.slice(3, -2).trim();
79
- }
80
- }
81
- docs.push(`- \`${propName}\` ${jsDoc}\n`);
82
- }
83
- }
84
- docs.sort();
85
- return docs.join("");
86
- };