drab 2.5.0 → 2.6.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.
@@ -39,7 +39,6 @@ Displays a list of `details` elements.
39
39
  icon={Chevron}
40
40
  classDetails="border-b"
41
41
  classHeader="flex gap-8 cursor-pointer items-center justify-between py-4 font-bold hover:underline"
42
- classContent="pb-4"
43
42
  items={[
44
43
  { summary: "Is it accessible?", content: "Yes." },
45
44
  {
@@ -54,7 +53,7 @@ Displays a list of `details` elements.
54
53
  ]}
55
54
  >
56
55
  <svelte:fragment slot="content" let:item let:index>
57
- <div class="mb-4">
56
+ <div class="pb-4">
58
57
  <span>{index + 1}.</span>
59
58
  <span>{item.content}</span>
60
59
  </div>
@@ -85,7 +85,6 @@ export type AccordionSlots = typeof __propDef.slots;
85
85
  * icon={Chevron}
86
86
  * classDetails="border-b"
87
87
  * classHeader="flex gap-8 cursor-pointer items-center justify-between py-4 font-bold hover:underline"
88
- * classContent="pb-4"
89
88
  * items={[
90
89
  * { summary: "Is it accessible?", content: "Yes." },
91
90
  * {
@@ -100,7 +99,7 @@ export type AccordionSlots = typeof __propDef.slots;
100
99
  * ]}
101
100
  * >
102
101
  * <svelte:fragment slot="content" let:item let:index>
103
- * <div class="mb-4">
102
+ * <div class="pb-4">
104
103
  * <span>{index + 1}.</span>
105
104
  * <span>{item.content}</span>
106
105
  * </div>
@@ -0,0 +1,71 @@
1
+ <!--
2
+ @component
3
+
4
+ ### Breakpoints
5
+
6
+ Displays the current breakpoint and `window.innerWidth`, based on the `breakpoints` provided. Defaults to [TailwindCSS breakpoint sizes](https://tailwindcss.com/docs/responsive-design).
7
+
8
+ ```svelte
9
+ <script lang="ts">
10
+ import { dev } from "$app/environment"; // SvelteKit Example
11
+ </script>
12
+
13
+ {#if dev}
14
+ <Breakpoint
15
+ class="fixed bottom-4 right-4 rounded border bg-white px-2 py-1 font-mono tabular-nums shadow"
16
+ />
17
+ {/if}
18
+ ```
19
+
20
+ @props
21
+
22
+ - `breakpoints` - array of objects representing the breakpoints of the application
23
+ - `class`
24
+ - `id`
25
+
26
+ @example
27
+
28
+ ```svelte
29
+ <script lang="ts">
30
+ import { Breakpoint } from "drab";
31
+ </script>
32
+
33
+ <div>
34
+ <Breakpoint
35
+ class="inline-block rounded border px-2 py-1 font-mono tabular-nums shadow"
36
+ />
37
+ </div>
38
+ ```
39
+ -->
40
+
41
+ <script>import { messageNoScript } from "../util/messages";
42
+ let className = "";
43
+ export { className as class };
44
+ export let id = "";
45
+ export let breakpoints = [
46
+ { name: "sm", width: 640 },
47
+ { name: "md", width: 768 },
48
+ { name: "lg", width: 1024 },
49
+ { name: "xl", width: 1280 },
50
+ { name: "2xl", width: 1536 }
51
+ ];
52
+ breakpoints.sort((a, b) => b.width - a.width);
53
+ let innerWidth = 0;
54
+ $:
55
+ breakpoint = getBreakpoint(innerWidth);
56
+ const getBreakpoint = (innerWidth2) => {
57
+ for (let i = 0; i < breakpoints.length; i++) {
58
+ if (innerWidth2 > breakpoints[i].width) {
59
+ return breakpoints[i].name;
60
+ }
61
+ }
62
+ return "none";
63
+ };
64
+ </script>
65
+
66
+ <svelte:window bind:innerWidth />
67
+
68
+ <div class={className} {id}>
69
+ {breakpoint}:{innerWidth}
70
+ <noscript>{messageNoScript}</noscript>
71
+ </div>
@@ -0,0 +1,58 @@
1
+ import { SvelteComponent } from "svelte";
2
+ declare const __propDef: {
3
+ props: {
4
+ class?: string | undefined;
5
+ id?: string | undefined;
6
+ /** array of objects representing the breakpoints of the application */ breakpoints?: {
7
+ name: string;
8
+ width: number;
9
+ }[] | undefined;
10
+ };
11
+ events: {
12
+ [evt: string]: CustomEvent<any>;
13
+ };
14
+ slots: {};
15
+ };
16
+ export type BreakpointProps = typeof __propDef.props;
17
+ export type BreakpointEvents = typeof __propDef.events;
18
+ export type BreakpointSlots = typeof __propDef.slots;
19
+ /**
20
+ * ### Breakpoints
21
+ *
22
+ * Displays the current breakpoint and `window.innerWidth`, based on the `breakpoints` provided. Defaults to [TailwindCSS breakpoint sizes](https://tailwindcss.com/docs/responsive-design).
23
+ *
24
+ * ```svelte
25
+ * <script lang="ts">
26
+ * import { dev } from "$app/environment"; // SvelteKit Example
27
+ * </script>
28
+ *
29
+ * {#if dev}
30
+ * <Breakpoint
31
+ * class="fixed bottom-4 right-4 rounded border bg-white px-2 py-1 font-mono tabular-nums shadow"
32
+ * />
33
+ * {/if}
34
+ * ```
35
+ *
36
+ * @props
37
+ *
38
+ * - `breakpoints` - array of objects representing the breakpoints of the application
39
+ * - `class`
40
+ * - `id`
41
+ *
42
+ * @example
43
+ *
44
+ * ```svelte
45
+ * <script lang="ts">
46
+ * import { Breakpoint } from "drab";
47
+ * </script>
48
+ *
49
+ * <div>
50
+ * <Breakpoint
51
+ * class="inline-block rounded border px-2 py-1 font-mono tabular-nums shadow"
52
+ * />
53
+ * </div>
54
+ * ```
55
+ */
56
+ export default class Breakpoint extends SvelteComponent<BreakpointProps, BreakpointEvents, BreakpointSlots> {
57
+ }
58
+ export {};
@@ -22,7 +22,7 @@ Generates a guitar chord `svg`.
22
22
  </script>
23
23
 
24
24
  <Chord
25
- class="text-gray-950"
25
+ class="text-neutral-950"
26
26
  name="D"
27
27
  notes={[
28
28
  {
@@ -46,7 +46,7 @@ export type ChordSlots = typeof __propDef.slots;
46
46
  * </script>
47
47
  *
48
48
  * <Chord
49
- * class="text-gray-950"
49
+ * class="text-neutral-950"
50
50
  * name="D"
51
51
  * notes={[
52
52
  * {
@@ -3,7 +3,7 @@
3
3
 
4
4
  ### ContextMenu
5
5
 
6
- Displays when the parent element is right clicked.
6
+ Displays when the parent element is right clicked, or long pressed on mobile.
7
7
 
8
8
  @props
9
9
 
@@ -11,6 +11,7 @@ Displays when the parent element is right clicked.
11
11
  - `class`
12
12
  - `display` - controls `display` css property
13
13
  - `id`
14
+ - `transition` - fades the content, set to `false` to remove
14
15
 
15
16
  @slots
16
17
 
@@ -27,12 +28,12 @@ Displays when the parent element is right clicked.
27
28
 
28
29
  <div class="flex justify-center rounded border border-dashed p-12">
29
30
  <div>Right click here</div>
30
- <ContextMenu class="transition">
31
- <div class="card flex w-48 flex-col gap-2">
31
+ <ContextMenu>
32
+ <div class="flex w-48 flex-col gap-2 rounded border bg-white p-2 shadow">
32
33
  <div class="font-bold">Context Menu</div>
33
- <button class="btn">Button</button>
34
- <button class="btn">Button</button>
35
- <button class="btn">Button</button>
34
+ <button role="menuitem" class="btn">Button</button>
35
+ <button role="menuitem" class="btn">Button</button>
36
+ <button role="menuitem" class="btn">Button</button>
36
37
  </div>
37
38
  </ContextMenu>
38
39
  </div>
@@ -40,12 +41,19 @@ Displays when the parent element is right clicked.
40
41
  -->
41
42
 
42
43
  <script>import { onMount, tick } from "svelte";
44
+ import { fade } from "svelte/transition";
45
+ import { duration } from "../util/transition";
46
+ import { messageNoScript } from "../util/messages";
47
+ import { prefersReducedMotion } from "../util/accessibility";
48
+ import { delay } from "../util/delay";
43
49
  let className = "";
44
50
  export { className as class };
45
51
  export let id = "";
46
52
  export let display = false;
47
53
  export let classNoscript = "";
54
+ export let transition = { duration };
48
55
  let contextMenu;
56
+ let base;
49
57
  let coordinates = { x: 0, y: 0 };
50
58
  const hide = () => display = false;
51
59
  const onKeyDown = (e) => {
@@ -53,58 +61,79 @@ const onKeyDown = (e) => {
53
61
  display = false;
54
62
  }
55
63
  };
64
+ const displayMenu = async (e) => {
65
+ e.preventDefault();
66
+ const scrollY = window.scrollY;
67
+ const scrollX = window.scrollX;
68
+ const clientX = e instanceof MouseEvent ? e.clientX : e.touches[0].clientX;
69
+ const clientY = e instanceof MouseEvent ? e.clientY : e.touches[0].clientY;
70
+ coordinates.x = clientX + scrollX;
71
+ coordinates.y = clientY + scrollY;
72
+ display = true;
73
+ await tick();
74
+ const offsetWidth = contextMenu.offsetWidth + 24;
75
+ const offsetHeight = contextMenu.offsetHeight + 6;
76
+ const innerWidth = window.innerWidth;
77
+ const innerHeight = window.innerHeight;
78
+ if (coordinates.x + offsetWidth > scrollX + innerWidth) {
79
+ coordinates.x = scrollX + innerWidth - offsetWidth;
80
+ }
81
+ if (coordinates.y + offsetHeight > scrollY + innerHeight) {
82
+ coordinates.y = scrollY + innerHeight - offsetHeight;
83
+ }
84
+ };
85
+ let timer;
86
+ const onTouchStart = (e) => {
87
+ timer = setTimeout(() => {
88
+ displayMenu(e);
89
+ }, delay);
90
+ };
91
+ const onTouchEnd = () => {
92
+ clearTimeout(timer);
93
+ };
56
94
  onMount(() => {
57
- const parentElement = contextMenu.parentElement;
95
+ if (prefersReducedMotion()) {
96
+ if (transition)
97
+ transition.duration = 0;
98
+ }
99
+ const parentElement = base.parentElement;
58
100
  if (parentElement) {
59
- parentElement.addEventListener("contextmenu", async (e) => {
60
- if (contextMenu) {
61
- e.preventDefault();
62
- const scrollY = window.scrollY;
63
- const scrollX = window.scrollX;
64
- coordinates.x = e.clientX + scrollX;
65
- coordinates.y = e.clientY + scrollY;
66
- display = true;
67
- await tick();
68
- const offsetWidth = contextMenu.offsetWidth + 24;
69
- const offsetHeight = contextMenu.offsetHeight + 6;
70
- const innerWidth = window.innerWidth;
71
- const innerHeight = window.innerHeight;
72
- if (coordinates.x + offsetWidth > scrollX + innerWidth) {
73
- coordinates.x = scrollX + innerWidth - offsetWidth;
74
- }
75
- if (coordinates.y + offsetHeight > scrollY + innerHeight) {
76
- coordinates.y = scrollY + innerHeight - offsetHeight;
77
- }
78
- }
79
- });
101
+ parentElement.addEventListener("contextmenu", displayMenu);
102
+ parentElement.addEventListener("touchstart", onTouchStart);
103
+ parentElement.addEventListener("touchend", onTouchEnd);
104
+ parentElement.addEventListener("touchcancel", onTouchEnd);
80
105
  }
81
106
  });
82
107
  </script>
83
108
 
84
109
  <svelte:body on:click={hide} on:keydown={onKeyDown} />
85
110
 
86
- <div
87
- class={className}
88
- {id}
89
- bind:this={contextMenu}
90
- style:z-index={display ? "10" : "-10"}
91
- style:opacity={display ? "100%" : "0%"}
92
- style:top="{coordinates.y}px"
93
- style:left="{coordinates.x}px"
94
- inert={display ? false : true}
95
- >
96
- <slot>Context Menu</slot>
97
- </div>
111
+ <span bind:this={base} role="presentation"></span>
98
112
 
99
- <noscript>
100
- <div class={classNoscript}>
101
- <slot />
113
+ {#if display}
114
+ <div
115
+ role="menu"
116
+ class={className}
117
+ {id}
118
+ bind:this={contextMenu}
119
+ style:top="{coordinates.y}px"
120
+ style:left="{coordinates.x}px"
121
+ transition:fade={transition ? transition : { duration: 0 }}
122
+ >
123
+ <slot>Context Menu</slot>
102
124
  </div>
103
- </noscript>
125
+ {/if}
126
+
127
+ <noscript><div class={classNoscript}>{messageNoScript}</div></noscript>
104
128
 
105
129
  <style>
130
+ span {
131
+ width: 0;
132
+ height: 0;
133
+ opacity: 0;
134
+ }
106
135
  div {
107
136
  position: absolute;
108
- z-index: 1;
137
+ z-index: 10;
109
138
  }
110
139
  </style>
@@ -1,10 +1,12 @@
1
1
  import { SvelteComponent } from "svelte";
2
+ import { type FadeParams } from "svelte/transition";
2
3
  declare const __propDef: {
3
4
  props: {
4
5
  class?: string | undefined;
5
6
  id?: string | undefined;
6
7
  /** controls `display` css property */ display?: boolean | undefined;
7
8
  /** `noscript` class */ classNoscript?: string | undefined;
9
+ /** fades the content, set to `false` to remove */ transition?: false | FadeParams | undefined;
8
10
  };
9
11
  events: {
10
12
  [evt: string]: CustomEvent<any>;
@@ -19,7 +21,7 @@ export type ContextMenuSlots = typeof __propDef.slots;
19
21
  /**
20
22
  * ### ContextMenu
21
23
  *
22
- * Displays when the parent element is right clicked.
24
+ * Displays when the parent element is right clicked, or long pressed on mobile.
23
25
  *
24
26
  * @props
25
27
  *
@@ -27,6 +29,7 @@ export type ContextMenuSlots = typeof __propDef.slots;
27
29
  * - `class`
28
30
  * - `display` - controls `display` css property
29
31
  * - `id`
32
+ * - `transition` - fades the content, set to `false` to remove
30
33
  *
31
34
  * @slots
32
35
  *
@@ -43,12 +46,12 @@ export type ContextMenuSlots = typeof __propDef.slots;
43
46
  *
44
47
  * <div class="flex justify-center rounded border border-dashed p-12">
45
48
  * <div>Right click here</div>
46
- * <ContextMenu class="transition">
47
- * <div class="card flex w-48 flex-col gap-2">
49
+ * <ContextMenu>
50
+ * <div class="flex w-48 flex-col gap-2 rounded border bg-white p-2 shadow">
48
51
  * <div class="font-bold">Context Menu</div>
49
- * <button class="btn">Button</button>
50
- * <button class="btn">Button</button>
51
- * <button class="btn">Button</button>
52
+ * <button role="menuitem" class="btn">Button</button>
53
+ * <button role="menuitem" class="btn">Button</button>
54
+ * <button role="menuitem" class="btn">Button</button>
52
55
  * </div>
53
56
  * </ContextMenu>
54
57
  * </div>
@@ -34,10 +34,11 @@ Uses the navigator api to copy text to the clipboard.
34
34
  -->
35
35
 
36
36
  <script>import { onMount } from "svelte";
37
+ import { delay } from "../util/delay";
37
38
  let className = "";
38
39
  export { className as class };
39
40
  export let id = "";
40
- export let title = "Copy";
41
+ export let title = "";
41
42
  export let text;
42
43
  export let classNoscript = "";
43
44
  let clientJs = false;
@@ -46,7 +47,7 @@ const copyText = async () => {
46
47
  try {
47
48
  await navigator.clipboard.writeText(text);
48
49
  complete = true;
49
- setTimeout(() => complete = false, 800);
50
+ setTimeout(() => complete = false, delay);
50
51
  } catch (error) {
51
52
  console.error(error);
52
53
  }
@@ -54,7 +55,14 @@ const copyText = async () => {
54
55
  onMount(() => clientJs = true);
55
56
  </script>
56
57
 
57
- <button disabled={!clientJs} on:click={copyText} class={className} {id} {title}>
58
+ <button
59
+ type="button"
60
+ disabled={!clientJs}
61
+ on:click={copyText}
62
+ class={className}
63
+ {id}
64
+ {title}
65
+ >
58
66
  {#if complete}
59
67
  <slot name="complete">Copied</slot>
60
68
  {:else}
@@ -62,7 +62,7 @@ Data table to display an array of JS objects. Click a column header to sort.
62
62
  class="tabular-nums"
63
63
  classTh="cursor-pointer uppercase"
64
64
  classThSorted="underline"
65
- classTbodyTr="transition hover:bg-gray-50"
65
+ classTbodyTr="transition hover:bg-neutral-50"
66
66
  classFooter="flex justify-between items-center"
67
67
  classButton="btn"
68
68
  />
@@ -101,7 +101,7 @@ export type DataTableSlots = typeof __propDef.slots;
101
101
  * class="tabular-nums"
102
102
  * classTh="cursor-pointer uppercase"
103
103
  * classThSorted="underline"
104
- * classTbodyTr="transition hover:bg-gray-50"
104
+ * classTbodyTr="transition hover:bg-neutral-50"
105
105
  * classFooter="flex justify-between items-center"
106
106
  * classButton="btn"
107
107
  * />
@@ -363,6 +363,7 @@ onMount(() => clientJs = true);
363
363
  <div id={idControls} class={classControls}>
364
364
  {#each contentElements as el}
365
365
  <button
366
+ type="button"
366
367
  class={el.class ? `${classButton} ${el.class}` : classButton}
367
368
  on:click={() => addContent(el)}
368
369
  title={el.name}
@@ -11,7 +11,7 @@ Make the document or a specific element fullscreen.
11
11
  - `class`
12
12
  - `confirmMessage` - message to display in the `confirm` popup, set this to empty string `""` to disable `confirm`
13
13
  - `id`
14
- - `targetElement` - element to make fullscreen (defaults to `document.documentElement` upon mount)
14
+ - `target` - element to make fullscreen (defaults to `document.documentElement` upon mount)
15
15
  - `title`
16
16
 
17
17
  @slots
@@ -36,10 +36,10 @@ Make the document or a specific element fullscreen.
36
36
 
37
37
  <div
38
38
  bind:this={fullscreenDiv}
39
- class="mt-4 rounded bg-gray-800 p-4 text-gray-50"
39
+ class="mt-4 rounded bg-neutral-800 p-4 text-neutral-50"
40
40
  >
41
41
  <div class="mb-2">Target element fullscreen</div>
42
- <FullscreenButton targetElement={fullscreenDiv} class="btn">
42
+ <FullscreenButton target={fullscreenDiv} class="btn btn-s bg-neutral-50">
43
43
  <span>Enable Element Fullscreen</span>
44
44
  <span slot="enabled">Exit Element Fullscreen</span>
45
45
  </FullscreenButton>
@@ -52,8 +52,8 @@ import { messageNoScript } from "../util/messages";
52
52
  let className = "";
53
53
  export { className as class };
54
54
  export let id = "";
55
- export let title = "Fullscreen";
56
- export let targetElement = null;
55
+ export let title = "";
56
+ export let target = null;
57
57
  export let confirmMessage = "";
58
58
  export let classNoscript = "";
59
59
  let supported = false;
@@ -62,13 +62,13 @@ const onClick = () => {
62
62
  if (fullscreen) {
63
63
  document.exitFullscreen();
64
64
  } else {
65
- if (targetElement && targetElement.requestFullscreen) {
65
+ if (target && target.requestFullscreen) {
66
66
  if (confirmMessage) {
67
67
  const permission = confirm(confirmMessage);
68
68
  if (permission)
69
- targetElement.requestFullscreen();
69
+ target.requestFullscreen();
70
70
  } else {
71
- targetElement.requestFullscreen();
71
+ target.requestFullscreen();
72
72
  }
73
73
  }
74
74
  }
@@ -76,14 +76,21 @@ const onClick = () => {
76
76
  onMount(() => {
77
77
  if (document.documentElement.requestFullscreen)
78
78
  supported = true;
79
- if (!targetElement)
80
- targetElement = document.documentElement;
79
+ if (!target)
80
+ target = document.documentElement;
81
81
  });
82
82
  </script>
83
83
 
84
84
  <svelte:window on:fullscreenchange={() => (fullscreen = !fullscreen)} />
85
85
 
86
- <button disabled={!supported} on:click={onClick} class={className} {id} {title}>
86
+ <button
87
+ type="button"
88
+ disabled={!supported}
89
+ on:click={onClick}
90
+ class={className}
91
+ {id}
92
+ {title}
93
+ >
87
94
  {#if fullscreen}
88
95
  <slot name="enabled">Exit Fullscreen</slot>
89
96
  {:else}
@@ -4,7 +4,7 @@ declare const __propDef: {
4
4
  class?: string | undefined;
5
5
  id?: string | undefined;
6
6
  title?: string | undefined;
7
- /** element to make fullscreen (defaults to `document.documentElement` upon mount) */ targetElement?: HTMLElement | null | undefined;
7
+ /** element to make fullscreen (defaults to `document.documentElement` upon mount) */ target?: HTMLElement | null | undefined;
8
8
  /** message to display in the `confirm` popup, set this to empty string `""` to disable `confirm` */ confirmMessage?: string | undefined;
9
9
  /** `noscript` class */ classNoscript?: string | undefined;
10
10
  };
@@ -30,7 +30,7 @@ export type FullscreenButtonSlots = typeof __propDef.slots;
30
30
  * - `class`
31
31
  * - `confirmMessage` - message to display in the `confirm` popup, set this to empty string `""` to disable `confirm`
32
32
  * - `id`
33
- * - `targetElement` - element to make fullscreen (defaults to `document.documentElement` upon mount)
33
+ * - `target` - element to make fullscreen (defaults to `document.documentElement` upon mount)
34
34
  * - `title`
35
35
  *
36
36
  * @slots
@@ -55,10 +55,10 @@ export type FullscreenButtonSlots = typeof __propDef.slots;
55
55
  *
56
56
  * <div
57
57
  * bind:this={fullscreenDiv}
58
- * class="mt-4 rounded bg-gray-800 p-4 text-gray-50"
58
+ * class="mt-4 rounded bg-neutral-800 p-4 text-neutral-50"
59
59
  * >
60
60
  * <div class="mb-2">Target element fullscreen</div>
61
- * <FullscreenButton targetElement={fullscreenDiv} class="btn">
61
+ * <FullscreenButton target={fullscreenDiv} class="btn btn-s bg-neutral-50">
62
62
  * <span>Enable Element Fullscreen</span>
63
63
  * <span slot="enabled">Exit Element Fullscreen</span>
64
64
  * </FullscreenButton>