lithesome 0.4.0 → 0.5.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,43 @@
1
+ <script context="module">import { getContext } from "svelte";
2
+ import { HovercardContext } from "./context.svelte.js";
3
+ const contextName = "hovercard-context";
4
+ export const context = () => getContext(contextName);
5
+ </script>
6
+
7
+ <script>import { useActions, classProp, parseDelay } from "../../internal/index.js";
8
+ import { setContext } from "svelte";
9
+ let {
10
+ children,
11
+ use = [],
12
+ class: klass,
13
+ visible = $bindable(false),
14
+ self = $bindable(),
15
+ delay = 700,
16
+ ...props
17
+ } = $props();
18
+ const delays = parseDelay(delay);
19
+ const ctx = new HovercardContext(
20
+ { visible, delays },
21
+ {
22
+ onChange(val) {
23
+ visible = val;
24
+ }
25
+ }
26
+ );
27
+ setContext(contextName, ctx);
28
+ $effect(() => {
29
+ ctx.visible = visible;
30
+ });
31
+ </script>
32
+
33
+ <div
34
+ bind:this={self}
35
+ use:useActions={use}
36
+ id={ctx.uid()}
37
+ class={classProp(klass, { visible: ctx.visible })}
38
+ data-hovercard=""
39
+ data-state={ctx.visible ? 'opened' : 'closed'}
40
+ {...props}
41
+ >
42
+ {@render children({ visible: ctx.visible })}
43
+ </div>
@@ -0,0 +1,30 @@
1
+ import { SvelteComponent } from "svelte";
2
+ import { HovercardContext } from './context.svelte.js';
3
+ export declare const context: () => HovercardContext;
4
+ import { type BaseProps } from '../../internal/index.js';
5
+ declare const __propDef: {
6
+ props: BaseProps<HTMLDivElement, {
7
+ visible: boolean;
8
+ }> & {
9
+ /**
10
+ * Whether or not the content is visible or not.
11
+ */
12
+ visible?: boolean | undefined;
13
+ /**
14
+ * The delay between the the content being visible or not.
15
+ *
16
+ * Passing an array will allow you to change the delays for in and out.
17
+ */
18
+ delay?: number | [number, number] | undefined;
19
+ };
20
+ events: {
21
+ [evt: string]: CustomEvent<any>;
22
+ };
23
+ slots: {};
24
+ };
25
+ export type HovercardProps = typeof __propDef.props;
26
+ export type HovercardEvents = typeof __propDef.events;
27
+ export type HovercardSlots = typeof __propDef.slots;
28
+ export default class Hovercard extends SvelteComponent<HovercardProps, HovercardEvents, HovercardSlots> {
29
+ }
30
+ export {};
@@ -0,0 +1,110 @@
1
+ <script>import { context } from "./Hovercard.svelte";
2
+ import {
3
+ clickOutside,
4
+ anchorElement,
5
+ portal,
6
+ useActions,
7
+ trap,
8
+ getTransition,
9
+ classProp
10
+ } from "../../internal/index.js";
11
+ import { log } from "../../internal/index.js";
12
+ import { onMount } from "svelte";
13
+ let {
14
+ children,
15
+ transition,
16
+ use = [],
17
+ portalTarget = "body",
18
+ class: klass,
19
+ self = $bindable(),
20
+ placement = "bottom",
21
+ constrainViewport,
22
+ sameWidth = false,
23
+ onMouseenter,
24
+ onMouseleave,
25
+ ...props
26
+ } = $props();
27
+ const ctx = context();
28
+ let contentCleanup = $state(void 0);
29
+ const _transition = getTransition(transition);
30
+ const attrs = $derived({
31
+ id: ctx.uid("content"),
32
+ "aria-labelledby": ctx.uid("trigger"),
33
+ class: classProp(klass, { visible: ctx.visible }),
34
+ "data-menucontent": "",
35
+ role: "menu",
36
+ tabindex: -1
37
+ });
38
+ onMount(async () => {
39
+ if (!ctx)
40
+ log.error("<HoverCardContent> Must be a direct child of <HoverCard />");
41
+ });
42
+ $effect(() => {
43
+ if (ctx.visible && self)
44
+ ctx.content = self;
45
+ });
46
+ $effect(() => {
47
+ if (ctx.visible && ctx.trigger && ctx.content) {
48
+ contentCleanup = anchorElement(ctx.trigger, ctx.content, {
49
+ placement,
50
+ constrainViewport,
51
+ sameWidth
52
+ });
53
+ }
54
+ return () => {
55
+ contentCleanup?.();
56
+ };
57
+ });
58
+ const handleMouseenter = (e) => {
59
+ onMouseenter?.(e);
60
+ ctx.hovered = true;
61
+ ctx.timeout = null;
62
+ };
63
+ const handleMouseleave = (e) => {
64
+ onMouseleave?.(e);
65
+ ctx.hovered = false;
66
+ ctx.close();
67
+ };
68
+ </script>
69
+
70
+ {#if _transition}
71
+ {#if ctx.visible}
72
+ <div
73
+ bind:this={self}
74
+ use:clickOutside={{
75
+ exclude: [ctx.trigger],
76
+ callback: () => {
77
+ ctx.visible = false;
78
+ }
79
+ }}
80
+ use:portal={portalTarget}
81
+ use:useActions={use}
82
+ in:_transition.in.fn={_transition.in.params}
83
+ out:_transition.out.fn={_transition.out.params}
84
+ onmouseenter={handleMouseenter}
85
+ onmouseleave={handleMouseleave}
86
+ {...attrs}
87
+ {...props}
88
+ >
89
+ {@render children({ visible: ctx.visible })}
90
+ </div>
91
+ {/if}
92
+ {:else if ctx.visible}
93
+ <div
94
+ bind:this={self}
95
+ use:clickOutside={{
96
+ exclude: [ctx.trigger],
97
+ callback: () => {
98
+ ctx.visible = false;
99
+ }
100
+ }}
101
+ use:portal={portalTarget}
102
+ use:useActions={use}
103
+ onmouseenter={handleMouseenter}
104
+ onmouseleave={handleMouseleave}
105
+ {...attrs}
106
+ {...props}
107
+ >
108
+ {@render children({ visible: ctx.visible })}
109
+ </div>
110
+ {/if}
@@ -0,0 +1,35 @@
1
+ import { SvelteComponent } from "svelte";
2
+ import { type Transition, type BaseProps, type Handler } from '../../internal/index.js';
3
+ import type { Placement } from '@floating-ui/dom';
4
+ declare const __propDef: {
5
+ props: BaseProps<HTMLDivElement, {
6
+ visible: boolean;
7
+ }> & {
8
+ /**
9
+ * The `svelte/transtion` you wish to use.
10
+ *
11
+ * @see https://lithesome.dev/docs/api#transition-prop
12
+ */
13
+ transition?: Transition | undefined;
14
+ /** The element to portal the content menu to. */
15
+ portalTarget?: string | HTMLElement | undefined;
16
+ /** The anchor point of the content relative to the trigger. */
17
+ placement?: Placement | undefined;
18
+ /** Keeps the content from ever growing outside of the viewport. */
19
+ constrainViewport?: boolean | undefined;
20
+ /** Makes the content the same width as the trigger. */
21
+ sameWidth?: boolean | undefined;
22
+ onMouseenter?: Handler<MouseEvent, HTMLDivElement> | undefined;
23
+ onMouseleave?: Handler<MouseEvent, HTMLDivElement> | undefined;
24
+ };
25
+ events: {
26
+ [evt: string]: CustomEvent<any>;
27
+ };
28
+ slots: {};
29
+ };
30
+ export type HovercardContentProps = typeof __propDef.props;
31
+ export type HovercardContentEvents = typeof __propDef.events;
32
+ export type HovercardContentSlots = typeof __propDef.slots;
33
+ export default class HovercardContent extends SvelteComponent<HovercardContentProps, HovercardContentEvents, HovercardContentSlots> {
34
+ }
35
+ export {};
@@ -0,0 +1,57 @@
1
+ <script>import { context } from "./Hovercard.svelte";
2
+ import {
3
+ log,
4
+ setNodeProps,
5
+ addEventListeners,
6
+ useActions,
7
+ removeNodeProps,
8
+ classProp
9
+ } from "../../internal/index.js";
10
+ import { onMount } from "svelte";
11
+ let { children, class: klass, use = [], self = $bindable(), ...props } = $props();
12
+ const ctx = context();
13
+ onMount(() => {
14
+ if (!self)
15
+ return;
16
+ if (self && self.children.length > 1) {
17
+ log.error("<HoverCardTrigger /> comoponent can only take 1 child node.");
18
+ return;
19
+ }
20
+ const target = self.children[0];
21
+ setNodeProps(target, {
22
+ id: ctx.uid("trigger"),
23
+ role: "button",
24
+ "aria-haspopup": "dialog",
25
+ "aria-expanded": "false"
26
+ });
27
+ addEventListeners(target, {
28
+ mouseenter: () => ctx.open(),
29
+ mouseleave: () => ctx.close()
30
+ });
31
+ ctx.trigger = target;
32
+ });
33
+ $effect(() => {
34
+ if (!ctx.trigger)
35
+ return;
36
+ const target = ctx.trigger;
37
+ if (ctx.visible) {
38
+ setNodeProps(target, {
39
+ "aria-expanded": "true",
40
+ "aria-controls": ctx.uid("content")
41
+ });
42
+ } else {
43
+ setNodeProps(target, { "aria-expanded": "false" });
44
+ removeNodeProps(target, "aria-controls");
45
+ }
46
+ });
47
+ </script>
48
+
49
+ <div
50
+ bind:this={self}
51
+ use:useActions={use}
52
+ class={classProp(klass, { visible: ctx.visible })}
53
+ data-hovercardtrigger=""
54
+ {...props}
55
+ >
56
+ {@render children({ visible: ctx.visible })}
57
+ </div>
@@ -0,0 +1,17 @@
1
+ import { SvelteComponent } from "svelte";
2
+ import { type BaseProps } from '../../internal/index.js';
3
+ declare const __propDef: {
4
+ props: BaseProps<HTMLDivElement, {
5
+ visible: boolean;
6
+ }>;
7
+ events: {
8
+ [evt: string]: CustomEvent<any>;
9
+ };
10
+ slots: {};
11
+ };
12
+ export type HovercardTriggerProps = typeof __propDef.props;
13
+ export type HovercardTriggerEvents = typeof __propDef.events;
14
+ export type HovercardTriggerSlots = typeof __propDef.slots;
15
+ export default class HovercardTrigger extends SvelteComponent<HovercardTriggerProps, HovercardTriggerEvents, HovercardTriggerSlots> {
16
+ }
17
+ export {};
@@ -0,0 +1,27 @@
1
+ import { Context } from '../../internal/index.js';
2
+ interface Init {
3
+ visible: boolean;
4
+ delays: {
5
+ in: number;
6
+ out: number;
7
+ };
8
+ }
9
+ interface Hooks {
10
+ onChange: (value: boolean) => void;
11
+ }
12
+ export declare class HovercardContext extends Context<Hooks> {
13
+ #private;
14
+ visible: boolean;
15
+ trigger: HTMLElement | null;
16
+ content: HTMLElement | null;
17
+ timeout: number | null;
18
+ delays: {
19
+ in: number;
20
+ out: number;
21
+ };
22
+ hovered: boolean;
23
+ constructor(init: Init, hooks: Hooks);
24
+ open(): void;
25
+ close(): void;
26
+ }
27
+ export {};
@@ -0,0 +1,36 @@
1
+ import { Context, effects } from '../../internal/index.js';
2
+ export class HovercardContext extends Context {
3
+ visible = $state(false);
4
+ trigger = $state(null);
5
+ content = $state(null);
6
+ timeout = $state(null);
7
+ delays = $state({ in: 700, out: 700 });
8
+ hovered = $state(false);
9
+ constructor(init, hooks) {
10
+ super('hovercard', hooks);
11
+ this.visible = init.visible;
12
+ this.delays = init.delays;
13
+ }
14
+ open() {
15
+ if (this.timeout) {
16
+ clearTimeout(this.timeout);
17
+ this.timeout = null;
18
+ }
19
+ this.timeout = setTimeout(() => {
20
+ this.visible = true;
21
+ }, this.delays.in);
22
+ }
23
+ close() {
24
+ if (this.timeout) {
25
+ clearTimeout(this.timeout);
26
+ this.timeout = null;
27
+ }
28
+ this.timeout = setTimeout(() => {
29
+ if (!this.hovered)
30
+ this.visible = false;
31
+ }, this.delays.out);
32
+ }
33
+ #effects = effects(() => {
34
+ this.hooks?.onChange?.(this.visible);
35
+ });
36
+ }
@@ -0,0 +1,3 @@
1
+ export { default as Hovercard } from './Hovercard.svelte';
2
+ export { default as HovercardTrigger } from './HovercardTrigger.svelte';
3
+ export { default as HovercardContent } from './HovercardContent.svelte';
@@ -0,0 +1,3 @@
1
+ export { default as Hovercard } from './Hovercard.svelte';
2
+ export { default as HovercardTrigger } from './HovercardTrigger.svelte';
3
+ export { default as HovercardContent } from './HovercardContent.svelte';
@@ -13,7 +13,7 @@ let { children, class: klass, use = [], self = $bindable(), onClick, onKeydown,
13
13
  const ctx = context();
14
14
  onMount(() => {
15
15
  if (self && self.children.length > 1) {
16
- log.error("<MenuTrigger /> comoponent can only take 1 children node.");
16
+ log.error("<MenuTrigger /> comoponent can only take 1 child node.");
17
17
  return;
18
18
  }
19
19
  const target = self?.children[0];
@@ -7,7 +7,6 @@ export const context = () => getContext(contextName);
7
7
  <script>import { createUID, useActions, KEYS, isBrowser, classProp } from "../../internal/index.js";
8
8
  import { setContext } from "svelte";
9
9
  let { children, use = [], class: klass, self = $bindable(), visible = $bindable(false), ...props } = $props();
10
- const { uid } = createUID("popover");
11
10
  const ctx = new PopoverContext(
12
11
  { visible },
13
12
  {
@@ -35,7 +34,7 @@ $effect(() => {
35
34
  <div
36
35
  bind:this={self}
37
36
  use:useActions={use}
38
- id={uid()}
37
+ id={ctx.uid()}
39
38
  class={classProp(klass, { visible: ctx.visible })}
40
39
  data-popover=""
41
40
  data-state={ctx.visible ? 'opened' : 'closed'}
@@ -5,21 +5,22 @@ import {
5
5
  addEventListeners,
6
6
  useActions,
7
7
  KEYS,
8
- classProp
8
+ classProp,
9
+ removeNodeProps
9
10
  } from "../../internal/index.js";
10
11
  import { onMount } from "svelte";
11
12
  let { children, class: klass, use = [], self = $bindable(), onClick, onKeydown, ...props } = $props();
12
13
  const ctx = context();
13
14
  onMount(() => {
14
15
  if (self && self.children.length > 1) {
15
- log.error("<MenuTrigger /> comoponent can only take 1 children node.");
16
+ log.error("<MenuTrigger /> comoponent can only take 1 child node.");
16
17
  return;
17
18
  }
18
19
  const target = self?.children[0];
19
20
  setNodeProps(target, {
20
21
  id: ctx.uid("trigger"),
21
22
  role: "button",
22
- "aria-hasdialog": "true",
23
+ "aria-haspopup": "dialog",
23
24
  "aria-expanded": "false"
24
25
  });
25
26
  addEventListeners(target, {
@@ -40,6 +41,7 @@ $effect(() => {
40
41
  }
41
42
  if (!ctx.visible) {
42
43
  setNodeProps(target, { "aria-expanded": "false" });
44
+ removeNodeProps(target, "aria-controls");
43
45
  }
44
46
  });
45
47
  const handleKeydown = (e) => {
@@ -39,7 +39,7 @@ const handleKeydown = (e) => {
39
39
  };
40
40
  onMount(() => {
41
41
  if (self && self.children.length > 1) {
42
- log.error("<SelectTrigger /> comoponent can only take 1 children node.");
42
+ log.error("<SelectTrigger /> comoponent can only take 1 child node.");
43
43
  return;
44
44
  }
45
45
  const target = self?.children[0];
package/dist/index.d.ts CHANGED
@@ -8,3 +8,4 @@ export * from './components/Select/index.js';
8
8
  export * from './components/Tabs/index.js';
9
9
  export * from './components/Popover/index.js';
10
10
  export * from './components/Combobox/index.js';
11
+ export * from './components/Hovercard/index.js';
package/dist/index.js CHANGED
@@ -8,3 +8,4 @@ export * from './components/Select/index.js';
8
8
  export * from './components/Tabs/index.js';
9
9
  export * from './components/Popover/index.js';
10
10
  export * from './components/Combobox/index.js';
11
+ export * from './components/Hovercard/index.js';
@@ -32,3 +32,10 @@ export declare const defaultConfig: <const T>(config: Partial<T> | undefined, de
32
32
  * @param props Any state to be passed down to the function.
33
33
  */
34
34
  export declare const classProp: <T extends Record<string, any>>(klass: ClassProp<T>, props?: T) => string | null | undefined;
35
+ /**
36
+ * Contructs a object with in and out properties.
37
+ */
38
+ export declare const parseDelay: (delay: number | [number, number]) => {
39
+ in: number;
40
+ out: number;
41
+ };
@@ -77,3 +77,12 @@ export const classProp = (klass, props) => {
77
77
  const cls = $derived(typeof klass === 'function' ? klass(_props) : klass);
78
78
  return cls;
79
79
  };
80
+ /**
81
+ * Contructs a object with in and out properties.
82
+ */
83
+ export const parseDelay = (delay) => {
84
+ return {
85
+ in: Array.isArray(delay) ? delay[0] : delay,
86
+ out: Array.isArray(delay) ? delay[1] : delay
87
+ };
88
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lithesome",
3
- "version": "0.4.0",
3
+ "version": "0.5.0",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "https://github.com/Gibbu/lithesome.git"