@dryui/ui 1.5.1 → 1.6.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.
Files changed (85) hide show
  1. package/dist/button-group/context.svelte.js +4 -7
  2. package/dist/calendar/calendar-root.svelte +15 -32
  3. package/dist/chip-group/context.svelte.d.ts +2 -4
  4. package/dist/chip-group/context.svelte.js +2 -9
  5. package/dist/context-menu/context-menu-content.svelte +24 -12
  6. package/dist/context-menu/context-menu-group.svelte +3 -2
  7. package/dist/context-menu/context-menu-item.svelte +8 -61
  8. package/dist/context-menu/context-menu-label.svelte +3 -11
  9. package/dist/context-menu/context-menu-root.svelte +10 -29
  10. package/dist/context-menu/context-menu-separator.svelte +2 -9
  11. package/dist/context-menu/context.svelte.d.ts +2 -12
  12. package/dist/date-picker/datepicker-content.svelte +11 -81
  13. package/dist/date-picker/datepicker-content.svelte.d.ts +1 -1
  14. package/dist/date-picker/datepicker-input-root.svelte +39 -47
  15. package/dist/date-range-picker/date-range-picker-content.svelte +11 -75
  16. package/dist/date-range-picker/date-range-picker-content.svelte.d.ts +1 -1
  17. package/dist/date-range-picker/date-range-picker-root.svelte +44 -49
  18. package/dist/drag-and-drop/group-context.svelte.d.ts +1 -1
  19. package/dist/drag-and-drop/group-context.svelte.js +4 -4
  20. package/dist/dropdown-menu/context.svelte.d.ts +2 -8
  21. package/dist/dropdown-menu/dropdown-menu-content.svelte +15 -3
  22. package/dist/dropdown-menu/dropdown-menu-group.svelte +3 -2
  23. package/dist/dropdown-menu/dropdown-menu-item.svelte +8 -61
  24. package/dist/dropdown-menu/dropdown-menu-label.svelte +3 -11
  25. package/dist/dropdown-menu/dropdown-menu-root.svelte +10 -21
  26. package/dist/dropdown-menu/dropdown-menu-separator.svelte +2 -9
  27. package/dist/flip-card/context.svelte.d.ts +5 -0
  28. package/dist/flip-card/context.svelte.js +2 -0
  29. package/dist/flip-card/flip-card-back.svelte +2 -2
  30. package/dist/flip-card/flip-card-root.svelte +42 -15
  31. package/dist/heading/heading.svelte +10 -1
  32. package/dist/heading/heading.svelte.d.ts +1 -0
  33. package/dist/heading/index.d.ts +1 -0
  34. package/dist/hover-card/hover-card-content.svelte +9 -21
  35. package/dist/hover-card/hover-card-root.svelte +2 -2
  36. package/dist/hover-card/hover-card-root.svelte.d.ts +4 -0
  37. package/dist/index.d.ts +2 -0
  38. package/dist/index.js +1 -0
  39. package/dist/internal/anchored-overlay-content.svelte.d.ts +20 -0
  40. package/dist/internal/anchored-overlay-content.svelte.js +28 -0
  41. package/dist/internal/date-family-controller.svelte.d.ts +45 -0
  42. package/dist/internal/date-family-controller.svelte.js +99 -0
  43. package/dist/internal/menu-group.svelte +15 -0
  44. package/dist/internal/menu-group.svelte.d.ts +9 -0
  45. package/dist/internal/menu-item.svelte +82 -0
  46. package/dist/internal/menu-item.svelte.d.ts +11 -0
  47. package/dist/internal/menu-label.svelte +24 -0
  48. package/dist/internal/menu-label.svelte.d.ts +9 -0
  49. package/dist/internal/menu-root-state.svelte.d.ts +24 -0
  50. package/dist/internal/menu-root-state.svelte.js +42 -0
  51. package/dist/internal/menu-separator.svelte +19 -0
  52. package/dist/internal/menu-separator.svelte.d.ts +7 -0
  53. package/dist/internal/motion.js +12 -1
  54. package/dist/internal/picker-popover-content.svelte +112 -0
  55. package/dist/internal/picker-popover-content.svelte.d.ts +16 -0
  56. package/dist/link-preview/link-preview-content.svelte +7 -10
  57. package/dist/list/list-item-icon.svelte +3 -3
  58. package/dist/list/list-item-icon.svelte.d.ts +1 -1
  59. package/dist/list/list-item-text.svelte +3 -3
  60. package/dist/list/list-item-text.svelte.d.ts +1 -1
  61. package/dist/list/list-item.svelte +58 -35
  62. package/dist/list/list-item.svelte.d.ts +8 -2
  63. package/dist/popover/popover-content.svelte +9 -11
  64. package/dist/range-calendar/range-calendar-root.svelte +13 -19
  65. package/dist/text/index.d.ts +1 -0
  66. package/dist/text/text.svelte +3 -1
  67. package/dist/text/text.svelte.d.ts +1 -0
  68. package/dist/theme-toggle/index.d.ts +18 -0
  69. package/dist/theme-toggle/index.js +3 -0
  70. package/dist/theme-toggle/theme-controller.svelte.d.ts +54 -0
  71. package/dist/theme-toggle/theme-controller.svelte.js +121 -0
  72. package/dist/theme-toggle/theme-flash.d.ts +16 -0
  73. package/dist/theme-toggle/theme-flash.js +38 -0
  74. package/dist/theme-toggle/theme-toggle.svelte +189 -0
  75. package/dist/theme-toggle/theme-toggle.svelte.d.ts +40 -0
  76. package/dist/tooltip/tooltip-content.svelte +8 -10
  77. package/dist/typography/heading.svelte +13 -89
  78. package/dist/typography/heading.svelte.d.ts +3 -8
  79. package/dist/typography/index.d.ts +8 -7
  80. package/dist/typography/text.svelte +12 -84
  81. package/dist/typography/text.svelte.d.ts +3 -10
  82. package/package.json +7 -2
  83. package/skills/dryui/SKILL.md +18 -5
  84. package/skills/dryui/rules/composition.md +1 -1
  85. package/skills/dryui/rules/theming.md +1 -2
@@ -1,11 +1,8 @@
1
- import { getContext, setContext, hasContext } from 'svelte';
2
- const KEY = Symbol('button-group');
1
+ import { createContext } from '@dryui/primitives';
2
+ const [_setButtonGroupCtx, _getButtonGroupCtx] = createContext('button-group');
3
3
  export function setButtonGroupCtx(ctx) {
4
- setContext(KEY, ctx);
5
- return ctx;
4
+ return _setButtonGroupCtx(ctx);
6
5
  }
7
6
  export function getButtonGroupCtx() {
8
- if (!hasContext(KEY))
9
- return undefined;
10
- return getContext(KEY);
7
+ return _getButtonGroupCtx();
11
8
  }
@@ -1,8 +1,8 @@
1
1
  <script lang="ts">
2
2
  import type { Snippet } from 'svelte';
3
3
  import type { HTMLAttributes } from 'svelte/elements';
4
+ import { createDateViewController } from '../internal/date-family-controller.svelte.js';
4
5
  import { setCalendarCtx } from './context.svelte.js';
5
- import { getWeekStartDay, addMonths } from '@dryui/primitives';
6
6
 
7
7
  interface Props extends HTMLAttributes<HTMLDivElement> {
8
8
  value?: Date | null;
@@ -24,24 +24,23 @@
24
24
  ...rest
25
25
  }: Props = $props();
26
26
 
27
- const weekStartDay = $derived(getWeekStartDay(locale));
28
-
29
- let viewMonth = $state(value ? value.getMonth() : new Date().getMonth());
30
- let viewYear = $state(value ? value.getFullYear() : new Date().getFullYear());
31
- let focusedDate = $state<Date>(value ?? new Date());
27
+ const view = createDateViewController({
28
+ initialDate: value,
29
+ locale: () => locale
30
+ });
32
31
 
33
32
  setCalendarCtx({
34
33
  get value() {
35
34
  return value;
36
35
  },
37
36
  get focusedDate() {
38
- return focusedDate;
37
+ return view.focusedDate;
39
38
  },
40
39
  get viewMonth() {
41
- return viewMonth;
40
+ return view.viewMonth;
42
41
  },
43
42
  get viewYear() {
44
- return viewYear;
43
+ return view.viewYear;
45
44
  },
46
45
  get locale() {
47
46
  return locale;
@@ -56,7 +55,7 @@
56
55
  return disabled;
57
56
  },
58
57
  get weekStartDay() {
59
- return weekStartDay;
58
+ return view.weekStartDay;
60
59
  },
61
60
  get multiple() {
62
61
  return false;
@@ -66,38 +65,22 @@
66
65
  },
67
66
  select(date: Date) {
68
67
  value = date;
69
- focusedDate = date;
70
- viewMonth = date.getMonth();
71
- viewYear = date.getFullYear();
68
+ view.setFocusedDate(date);
72
69
  },
73
70
  goToMonth(month: number) {
74
- if (month < 0) {
75
- viewMonth = 11;
76
- viewYear = viewYear - 1;
77
- } else if (month > 11) {
78
- viewMonth = 0;
79
- viewYear = viewYear + 1;
80
- } else {
81
- viewMonth = month;
82
- }
71
+ view.goToMonth(month);
83
72
  },
84
73
  goToYear(year: number) {
85
- viewYear = year;
74
+ view.goToYear(year);
86
75
  },
87
76
  nextMonth() {
88
- const next = addMonths(new Date(viewYear, viewMonth, 1), 1);
89
- viewMonth = next.getMonth();
90
- viewYear = next.getFullYear();
77
+ view.nextMonth();
91
78
  },
92
79
  prevMonth() {
93
- const prev = addMonths(new Date(viewYear, viewMonth, 1), -1);
94
- viewMonth = prev.getMonth();
95
- viewYear = prev.getFullYear();
80
+ view.prevMonth();
96
81
  },
97
82
  setFocusedDate(date: Date) {
98
- focusedDate = date;
99
- viewMonth = date.getMonth();
100
- viewYear = date.getFullYear();
83
+ view.setFocusedDate(date);
101
84
  }
102
85
  });
103
86
  </script>
@@ -1,10 +1,8 @@
1
- interface ChipGroupContext {
1
+ export interface ChipGroupContext {
2
2
  readonly type: 'single' | 'multiple';
3
3
  readonly disabled: boolean;
4
4
  readonly value: string[];
5
5
  toggle: (itemValue: string) => void;
6
6
  isSelected: (itemValue: string) => boolean;
7
7
  }
8
- export declare function setChipGroupCtx(ctx: ChipGroupContext): ChipGroupContext;
9
- export declare function getChipGroupCtx(): ChipGroupContext;
10
- export {};
8
+ export declare const setChipGroupCtx: (ctx: ChipGroupContext) => ChipGroupContext, getChipGroupCtx: () => ChipGroupContext;
@@ -1,9 +1,2 @@
1
- import { getContext, setContext } from 'svelte';
2
- const CHIP_GROUP_KEY = Symbol('chip-group');
3
- export function setChipGroupCtx(ctx) {
4
- setContext(CHIP_GROUP_KEY, ctx);
5
- return ctx;
6
- }
7
- export function getChipGroupCtx() {
8
- return getContext(CHIP_GROUP_KEY);
9
- }
1
+ import { createContext } from '@dryui/primitives';
2
+ export const [setChipGroupCtx, getChipGroupCtx] = createContext('chip-group');
@@ -12,26 +12,36 @@
12
12
 
13
13
  const ctx = getContextMenuCtx();
14
14
 
15
- let el = $state<HTMLDivElement>();
15
+ let el = $state<HTMLDivElement | null>(null);
16
+
17
+ function attachContent(node: HTMLDivElement) {
18
+ el = node;
19
+
20
+ return () => {
21
+ if (el === node) {
22
+ el = null;
23
+ }
24
+ };
25
+ }
16
26
 
17
27
  const menu = createMenuNavigation({
18
28
  container: () => el ?? null,
19
29
  orientation: 'vertical'
20
30
  });
21
31
 
22
- $effect(() => {
23
- if (ctx.open && el) {
24
- el.style.position = 'fixed';
25
- el.style.left = `${ctx.position.x}px`;
26
- el.style.top = `${ctx.position.y}px`;
27
- if (!el.matches(':popover-open')) {
28
- el.showPopover();
32
+ function syncPopover(node: HTMLDivElement) {
33
+ if (ctx.open) {
34
+ node.style.position = 'fixed';
35
+ node.style.left = `${ctx.position.x}px`;
36
+ node.style.top = `${ctx.position.y}px`;
37
+ if (!node.matches(':popover-open')) {
38
+ node.showPopover();
29
39
  menu.focusFirst();
30
40
  }
31
- } else if (!ctx.open && el?.matches(':popover-open')) {
32
- el.hidePopover();
41
+ } else if (node.matches(':popover-open')) {
42
+ node.hidePopover();
33
43
  }
34
- });
44
+ }
35
45
 
36
46
  createDismiss({
37
47
  enabled: () => ctx.open,
@@ -42,7 +52,8 @@
42
52
  </script>
43
53
 
44
54
  <div
45
- bind:this={el}
55
+ {@attach attachContent}
56
+ {@attach syncPopover}
46
57
  popover="manual"
47
58
  role="menu"
48
59
  tabindex="-1"
@@ -69,6 +80,7 @@
69
80
  --dry-menu-radius: var(--dry-radius-lg);
70
81
  --dry-menu-shadow: var(--dry-shadow-lg);
71
82
  --dry-menu-padding: var(--dry-space-1);
83
+ --dry-menu-item-padding: var(--dry-space-2_5) var(--dry-space-2);
72
84
 
73
85
  display: grid;
74
86
  grid-template-columns: minmax(12rem, max-content);
@@ -1,6 +1,7 @@
1
1
  <script lang="ts">
2
2
  import type { Snippet } from 'svelte';
3
3
  import type { HTMLAttributes } from 'svelte/elements';
4
+ import MenuGroup from '../internal/menu-group.svelte';
4
5
 
5
6
  interface Props extends HTMLAttributes<HTMLDivElement> {
6
7
  children: Snippet;
@@ -9,6 +10,6 @@
9
10
  let { class: className, children, ...rest }: Props = $props();
10
11
  </script>
11
12
 
12
- <div role="group" class={className} {...rest}>
13
+ <MenuGroup {className} {...rest}>
13
14
  {@render children()}
14
- </div>
15
+ </MenuGroup>
@@ -2,6 +2,7 @@
2
2
  import type { Snippet } from 'svelte';
3
3
  import type { HTMLAttributes } from 'svelte/elements';
4
4
  import { getContextMenuCtx } from './context.svelte.js';
5
+ import MenuItem from '../internal/menu-item.svelte';
5
6
 
6
7
  interface Props extends HTMLAttributes<HTMLDivElement> {
7
8
  disabled?: boolean;
@@ -11,70 +12,16 @@
11
12
  let { class: className, disabled, children, onclick, onkeydown, ...rest }: Props = $props();
12
13
 
13
14
  const ctx = getContextMenuCtx();
14
-
15
- function handleClick(e: MouseEvent & { currentTarget: HTMLDivElement }) {
16
- if (disabled) return;
17
- if (onclick) (onclick as (e: MouseEvent & { currentTarget: HTMLDivElement }) => void)(e);
18
- ctx.close();
19
- }
20
-
21
- function handleKeydown(e: KeyboardEvent & { currentTarget: HTMLDivElement }) {
22
- if (disabled) return;
23
- if (e.key === 'Enter' || e.key === ' ') {
24
- e.preventDefault();
25
- (e.currentTarget as HTMLElement).click();
26
- }
27
- if (onkeydown) (onkeydown as (e: KeyboardEvent & { currentTarget: HTMLDivElement }) => void)(e);
28
- }
29
15
  </script>
30
16
 
31
- <div
32
- role="menuitem"
33
- tabindex={disabled ? undefined : -1}
34
- aria-disabled={disabled || undefined}
17
+ <MenuItem
35
18
  data-context-menu-item
36
- data-disabled={disabled || undefined}
37
- class={className}
38
- onclick={handleClick}
39
- onkeydown={handleKeydown}
19
+ {className}
20
+ close={ctx.close}
21
+ {disabled}
22
+ {onclick}
23
+ {onkeydown}
40
24
  {...rest}
41
25
  >
42
26
  {@render children()}
43
- </div>
44
-
45
- <style>
46
- [data-context-menu-item] {
47
- display: grid;
48
- grid-auto-flow: column;
49
- grid-auto-columns: max-content;
50
- align-items: center;
51
- gap: var(--dry-space-2);
52
- padding: var(--dry-menu-item-padding, var(--dry-space-2_5) var(--dry-space-2));
53
- border-radius: var(
54
- --dry-menu-item-radius,
55
- min(var(--dry-control-radius, var(--dry-radius-sm)), var(--dry-space-4))
56
- );
57
- font-size: var(--dry-type-small-size, var(--dry-text-sm-size));
58
- cursor: pointer;
59
- user-select: none;
60
- outline: none;
61
- color: var(--dry-color-text-strong);
62
- min-height: var(--dry-space-11);
63
- transition: background var(--dry-duration-fast) var(--dry-ease-default);
64
- }
65
-
66
- [data-context-menu-item]:hover:not([data-disabled]),
67
- [data-context-menu-item]:focus-visible {
68
- background: var(--dry-color-fill);
69
- }
70
-
71
- [data-context-menu-item]:active:not([data-disabled]) {
72
- background: var(--dry-color-fill-hover);
73
- }
74
-
75
- [data-context-menu-item][data-disabled] {
76
- color: var(--dry-color-text-disabled);
77
- cursor: not-allowed;
78
- pointer-events: none;
79
- }
80
- </style>
27
+ </MenuItem>
@@ -1,6 +1,7 @@
1
1
  <script lang="ts">
2
2
  import type { Snippet } from 'svelte';
3
3
  import type { HTMLAttributes } from 'svelte/elements';
4
+ import MenuLabel from '../internal/menu-label.svelte';
4
5
 
5
6
  interface Props extends HTMLAttributes<HTMLDivElement> {
6
7
  children: Snippet;
@@ -9,15 +10,6 @@
9
10
  let { class: className, children, ...rest }: Props = $props();
10
11
  </script>
11
12
 
12
- <div role="presentation" data-context-menu-label class={className} {...rest}>
13
+ <MenuLabel data-context-menu-label {className} {...rest}>
13
14
  {@render children()}
14
- </div>
15
-
16
- <style>
17
- [data-context-menu-label] {
18
- padding: var(--dry-space-1_5) var(--dry-space-2);
19
- font-size: var(--dry-type-tiny-size, var(--dry-text-xs-size));
20
- color: var(--dry-color-text-weak);
21
- font-weight: 500;
22
- }
23
- </style>
15
+ </MenuLabel>
@@ -1,7 +1,7 @@
1
1
  <script lang="ts">
2
2
  import type { Snippet } from 'svelte';
3
- import { generateFormId } from '@dryui/primitives';
4
3
  import { setContextMenuCtx } from './context.svelte.js';
4
+ import { createPositionedMenuRootState } from '../internal/menu-root-state.svelte.js';
5
5
 
6
6
  interface Props {
7
7
  open?: boolean;
@@ -10,34 +10,15 @@
10
10
 
11
11
  let { open = $bindable(false), children }: Props = $props();
12
12
 
13
- const triggerId = generateFormId('context-menu-trigger');
14
- const contentId = generateFormId('context-menu-content');
15
-
16
- let position = $state({ x: 0, y: 0 });
17
-
18
- setContextMenuCtx({
19
- get open() {
20
- return open;
21
- },
22
- triggerId,
23
- contentId,
24
- triggerEl: null,
25
- get position() {
26
- return position;
27
- },
28
- set position(value: { x: number; y: number }) {
29
- position = value;
30
- },
31
- show() {
32
- open = true;
33
- },
34
- close() {
35
- open = false;
36
- },
37
- toggle() {
38
- open = !open;
39
- }
40
- });
13
+ setContextMenuCtx(
14
+ createPositionedMenuRootState({
15
+ idBase: 'context-menu',
16
+ getOpen: () => open,
17
+ setOpen: (value) => {
18
+ open = value;
19
+ }
20
+ })
21
+ );
41
22
  </script>
42
23
 
43
24
  {@render children()}
@@ -1,17 +1,10 @@
1
1
  <script lang="ts">
2
2
  import type { HTMLAttributes } from 'svelte/elements';
3
+ import MenuSeparator from '../internal/menu-separator.svelte';
3
4
 
4
5
  interface Props extends HTMLAttributes<HTMLDivElement> {}
5
6
 
6
7
  let { class: className, ...rest }: Props = $props();
7
8
  </script>
8
9
 
9
- <div role="separator" data-context-menu-separator class={className} {...rest}></div>
10
-
11
- <style>
12
- [data-context-menu-separator] {
13
- height: 1px;
14
- background: var(--dry-color-stroke-weak);
15
- margin: var(--dry-space-1) 0;
16
- }
17
- </style>
10
+ <MenuSeparator data-context-menu-separator {className} {...rest}></MenuSeparator>
@@ -1,14 +1,4 @@
1
- export interface ContextMenuContext {
2
- readonly open: boolean;
3
- readonly triggerId: string;
4
- readonly contentId: string;
5
- triggerEl: HTMLElement | null;
6
- position: {
7
- x: number;
8
- y: number;
9
- };
10
- show: () => void;
11
- close: () => void;
12
- toggle: () => void;
1
+ import type { PositionedMenuRootState } from '../internal/menu-root-state.svelte.js';
2
+ export interface ContextMenuContext extends PositionedMenuRootState {
13
3
  }
14
4
  export declare const setContextMenuCtx: (ctx: ContextMenuContext) => ContextMenuContext, getContextMenuCtx: () => ContextMenuContext;
@@ -1,8 +1,8 @@
1
1
  <script lang="ts">
2
- import { fromAction } from 'svelte/attachments';
3
2
  import type { Snippet } from 'svelte';
4
3
  import type { HTMLAttributes } from 'svelte/elements';
5
- import { createAnchoredPopover, type Placement } from '@dryui/primitives';
4
+ import type { Placement } from '@dryui/primitives';
5
+ import PickerPopoverContent from '../internal/picker-popover-content.svelte';
6
6
  import { getDatePickerCtx } from './context.svelte.js';
7
7
 
8
8
  interface Props extends HTMLAttributes<HTMLDivElement> {
@@ -21,85 +21,15 @@
21
21
  }: Props = $props();
22
22
 
23
23
  const ctx = getDatePickerCtx();
24
-
25
- let el = $state<HTMLDivElement | null>(null);
26
-
27
- function attachContent(node: HTMLDivElement) {
28
- el = node;
29
-
30
- return () => {
31
- if (el === node) {
32
- el = null;
33
- }
34
- };
35
- }
36
-
37
- const popover = createAnchoredPopover({
38
- triggerEl: () => ctx.triggerEl,
39
- contentEl: () => el ?? null,
40
- open: () => ctx.open,
41
- placement: () => placement,
42
- offset: () => offset
43
- });
44
24
  </script>
45
25
 
46
- <div
47
- {@attach attachContent}
48
- {@attach fromAction(popover.applyPosition, () => style)}
49
- popover="auto"
50
- role="dialog"
51
- id={ctx.contentId}
52
- aria-labelledby={ctx.triggerId}
53
- data-state={ctx.open ? 'open' : 'closed'}
54
- data-dp-content
55
- class={className}
56
- ontoggle={(e) => {
57
- const newState = (e as ToggleEvent).newState === 'open';
58
- if (newState && !ctx.open) {
59
- ctx.show();
60
- } else if (!newState && ctx.open) {
61
- ctx.close();
62
- }
63
- }}
26
+ <PickerPopoverContent
27
+ controller={ctx}
28
+ dataAttribute="data-dp-content"
29
+ {placement}
30
+ {offset}
31
+ contentStyle={style}
32
+ contentClass={className}
33
+ {children}
64
34
  {...rest}
65
- >
66
- {@render children()}
67
- </div>
68
-
69
- <style>
70
- [data-dp-content] {
71
- inset: unset;
72
- margin: 0;
73
- display: inline-grid;
74
-
75
- background: var(--dry-color-bg-overlay);
76
- border: 1px solid var(--dry-color-stroke-weak);
77
- border-radius: var(--dry-radius-lg);
78
- box-shadow: var(--dry-shadow-lg);
79
- padding: var(--dry-space-3);
80
- }
81
-
82
- [data-dp-content]:not(:popover-open) {
83
- display: none;
84
- }
85
-
86
- [data-dp-content]:popover-open {
87
- display: inline-grid;
88
- opacity: 1;
89
- transform: scale(1) translateY(0);
90
- }
91
-
92
- @starting-style {
93
- [data-dp-content]:popover-open {
94
- opacity: 0;
95
- transform: scale(var(--dry-motion-scale-enter))
96
- translateY(calc(var(--dry-motion-distance-xs) * -1));
97
- }
98
- }
99
-
100
- [data-dp-content] {
101
- transition:
102
- opacity var(--dry-duration-fast) var(--dry-ease-emphasized),
103
- transform var(--dry-duration-fast) var(--dry-ease-emphasized);
104
- }
105
- </style>
35
+ />
@@ -1,6 +1,6 @@
1
1
  import type { Snippet } from 'svelte';
2
2
  import type { HTMLAttributes } from 'svelte/elements';
3
- import { type Placement } from '@dryui/primitives';
3
+ import type { Placement } from '@dryui/primitives';
4
4
  interface Props extends HTMLAttributes<HTMLDivElement> {
5
5
  placement?: Placement;
6
6
  offset?: number;
@@ -1,8 +1,10 @@
1
1
  <script lang="ts">
2
2
  import type { Snippet } from 'svelte';
3
- import { generateFormId } from '@dryui/primitives';
3
+ import {
4
+ createDateViewController,
5
+ createPickerPopoverController
6
+ } from '../internal/date-family-controller.svelte.js';
4
7
  import { setDatePickerCtx } from './context.svelte.js';
5
- import { getWeekStartDay, addMonths } from '@dryui/primitives';
6
8
 
7
9
  interface Props {
8
10
  open?: boolean;
@@ -26,18 +28,20 @@
26
28
  children
27
29
  }: Props = $props();
28
30
 
29
- const weekStartDay = $derived(getWeekStartDay(locale));
30
-
31
- const triggerId = generateFormId('datepicker-trigger');
32
- const contentId = generateFormId('datepicker-content');
33
-
34
- // View state: which month/year is the calendar showing
35
- let viewMonth = $state(value ? value.getMonth() : new Date().getMonth());
36
- let viewYear = $state(value ? value.getFullYear() : new Date().getFullYear());
37
- let triggerEl = $state<HTMLElement | null>(null);
31
+ const view = createDateViewController({
32
+ initialDate: value,
33
+ locale: () => locale
34
+ });
38
35
 
39
- // The day that has keyboard focus within the calendar grid
40
- let focusedDate = $state<Date>(value ?? new Date());
36
+ const popover = createPickerPopoverController({
37
+ triggerIdPrefix: 'datepicker-trigger',
38
+ contentIdPrefix: 'datepicker-content',
39
+ open: () => open,
40
+ setOpen: (nextOpen) => {
41
+ open = nextOpen;
42
+ },
43
+ disabled: () => disabled
44
+ });
41
45
 
42
46
  function serializeDateValue(date: Date | null): string {
43
47
  if (!date) return '';
@@ -57,13 +61,13 @@
57
61
  return value;
58
62
  },
59
63
  get focusedDate() {
60
- return focusedDate;
64
+ return view.focusedDate;
61
65
  },
62
66
  get viewMonth() {
63
- return viewMonth;
67
+ return view.viewMonth;
64
68
  },
65
69
  get viewYear() {
66
- return viewYear;
70
+ return view.viewYear;
67
71
  },
68
72
  get locale() {
69
73
  return locale;
@@ -78,60 +82,48 @@
78
82
  return disabled;
79
83
  },
80
84
  get weekStartDay() {
81
- return weekStartDay;
85
+ return view.weekStartDay;
86
+ },
87
+ get triggerId() {
88
+ return popover.triggerId;
89
+ },
90
+ get contentId() {
91
+ return popover.contentId;
82
92
  },
83
- triggerId,
84
- contentId,
85
93
  get triggerEl() {
86
- return triggerEl;
94
+ return popover.triggerEl;
87
95
  },
88
96
  set triggerEl(element: HTMLElement | null) {
89
- triggerEl = element;
97
+ popover.setTriggerEl(element);
90
98
  },
91
99
  show() {
92
- if (!disabled) open = true;
100
+ popover.show();
93
101
  },
94
102
  close() {
95
- open = false;
103
+ popover.close();
96
104
  },
97
105
  toggle() {
98
- if (!disabled) open = !open;
106
+ popover.toggle();
99
107
  },
100
108
  select(date: Date) {
101
109
  value = date;
102
- focusedDate = date;
103
- viewMonth = date.getMonth();
104
- viewYear = date.getFullYear();
105
- open = false;
110
+ view.setFocusedDate(date);
111
+ popover.close();
106
112
  },
107
113
  goToMonth(month: number) {
108
- if (month < 0) {
109
- viewMonth = 11;
110
- viewYear = viewYear - 1;
111
- } else if (month > 11) {
112
- viewMonth = 0;
113
- viewYear = viewYear + 1;
114
- } else {
115
- viewMonth = month;
116
- }
114
+ view.goToMonth(month);
117
115
  },
118
116
  goToYear(year: number) {
119
- viewYear = year;
117
+ view.goToYear(year);
120
118
  },
121
119
  nextMonth() {
122
- const next = addMonths(new Date(viewYear, viewMonth, 1), 1);
123
- viewMonth = next.getMonth();
124
- viewYear = next.getFullYear();
120
+ view.nextMonth();
125
121
  },
126
122
  prevMonth() {
127
- const prev = addMonths(new Date(viewYear, viewMonth, 1), -1);
128
- viewMonth = prev.getMonth();
129
- viewYear = prev.getFullYear();
123
+ view.prevMonth();
130
124
  },
131
125
  setFocusedDate(date: Date) {
132
- focusedDate = date;
133
- viewMonth = date.getMonth();
134
- viewYear = date.getFullYear();
126
+ view.setFocusedDate(date);
135
127
  }
136
128
  });
137
129
  </script>