@dryui/ui 1.5.1 → 1.7.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 (118) hide show
  1. package/dist/accordion/accordion-item.svelte +9 -0
  2. package/dist/accordion/accordion-root.svelte +1 -1
  3. package/dist/alert/alert.svelte +1 -0
  4. package/dist/avatar/avatar.svelte +1 -1
  5. package/dist/button/button.svelte +3 -2
  6. package/dist/button-group/context.svelte.js +4 -7
  7. package/dist/calendar/calendar-root.svelte +15 -32
  8. package/dist/card/card-root.svelte +1 -0
  9. package/dist/chart/chart-y-axis.svelte +5 -0
  10. package/dist/checkbox/checkbox-input.svelte +30 -31
  11. package/dist/chip-group/context.svelte.d.ts +2 -4
  12. package/dist/chip-group/context.svelte.js +2 -9
  13. package/dist/combobox/combobox-content.svelte +1 -0
  14. package/dist/combobox/combobox-item.svelte +9 -0
  15. package/dist/command-palette/command-palette-item.svelte +9 -0
  16. package/dist/command-palette/command-palette-list.svelte +1 -0
  17. package/dist/context-menu/context-menu-content.svelte +25 -12
  18. package/dist/context-menu/context-menu-group.svelte +3 -2
  19. package/dist/context-menu/context-menu-item.svelte +8 -61
  20. package/dist/context-menu/context-menu-label.svelte +3 -11
  21. package/dist/context-menu/context-menu-root.svelte +10 -29
  22. package/dist/context-menu/context-menu-separator.svelte +2 -9
  23. package/dist/context-menu/context.svelte.d.ts +2 -12
  24. package/dist/data-grid/data-grid-cell.svelte +5 -0
  25. package/dist/date-picker/datepicker-content.svelte +11 -81
  26. package/dist/date-picker/datepicker-content.svelte.d.ts +1 -1
  27. package/dist/date-picker/datepicker-input-root.svelte +39 -47
  28. package/dist/date-range-picker/date-range-picker-content.svelte +11 -75
  29. package/dist/date-range-picker/date-range-picker-content.svelte.d.ts +1 -1
  30. package/dist/date-range-picker/date-range-picker-root.svelte +44 -49
  31. package/dist/drag-and-drop/group-context.svelte.d.ts +1 -1
  32. package/dist/drag-and-drop/group-context.svelte.js +4 -4
  33. package/dist/dropdown-menu/context.svelte.d.ts +2 -8
  34. package/dist/dropdown-menu/dropdown-menu-content.svelte +22 -3
  35. package/dist/dropdown-menu/dropdown-menu-group.svelte +3 -2
  36. package/dist/dropdown-menu/dropdown-menu-item.svelte +8 -61
  37. package/dist/dropdown-menu/dropdown-menu-label.svelte +3 -11
  38. package/dist/dropdown-menu/dropdown-menu-root.svelte +10 -21
  39. package/dist/dropdown-menu/dropdown-menu-separator.svelte +2 -9
  40. package/dist/flip-card/context.svelte.d.ts +5 -0
  41. package/dist/flip-card/context.svelte.js +2 -0
  42. package/dist/flip-card/flip-card-back.svelte +2 -2
  43. package/dist/flip-card/flip-card-root.svelte +42 -15
  44. package/dist/heading/heading.svelte +10 -1
  45. package/dist/heading/heading.svelte.d.ts +1 -0
  46. package/dist/heading/index.d.ts +1 -0
  47. package/dist/hover-card/hover-card-content.svelte +9 -21
  48. package/dist/hover-card/hover-card-root.svelte +2 -2
  49. package/dist/hover-card/hover-card-root.svelte.d.ts +4 -0
  50. package/dist/image/image.svelte +5 -0
  51. package/dist/index.d.ts +2 -0
  52. package/dist/index.js +1 -0
  53. package/dist/internal/anchored-overlay-content.svelte.d.ts +20 -0
  54. package/dist/internal/anchored-overlay-content.svelte.js +28 -0
  55. package/dist/internal/date-family-controller.svelte.d.ts +45 -0
  56. package/dist/internal/date-family-controller.svelte.js +99 -0
  57. package/dist/internal/menu-group.svelte +15 -0
  58. package/dist/internal/menu-group.svelte.d.ts +9 -0
  59. package/dist/internal/menu-item.svelte +91 -0
  60. package/dist/internal/menu-item.svelte.d.ts +11 -0
  61. package/dist/internal/menu-label.svelte +24 -0
  62. package/dist/internal/menu-label.svelte.d.ts +9 -0
  63. package/dist/internal/menu-root-state.svelte.d.ts +24 -0
  64. package/dist/internal/menu-root-state.svelte.js +42 -0
  65. package/dist/internal/menu-separator.svelte +19 -0
  66. package/dist/internal/menu-separator.svelte.d.ts +7 -0
  67. package/dist/internal/modal-content.svelte +18 -0
  68. package/dist/internal/motion.js +12 -1
  69. package/dist/internal/nav-arrow-button.svelte +21 -5
  70. package/dist/internal/picker-popover-content.svelte +112 -0
  71. package/dist/internal/picker-popover-content.svelte.d.ts +16 -0
  72. package/dist/link-preview/link-preview-content.svelte +7 -10
  73. package/dist/list/list-item-icon.svelte +3 -3
  74. package/dist/list/list-item-icon.svelte.d.ts +1 -1
  75. package/dist/list/list-item-text.svelte +3 -3
  76. package/dist/list/list-item-text.svelte.d.ts +1 -1
  77. package/dist/list/list-item.svelte +58 -35
  78. package/dist/list/list-item.svelte.d.ts +8 -2
  79. package/dist/menubar/menubar-content.svelte +1 -0
  80. package/dist/menubar/menubar-item.svelte +10 -1
  81. package/dist/number-input/number-input-button.svelte +1 -0
  82. package/dist/pin-input/pin-input-cell.svelte +1 -0
  83. package/dist/popover/popover-content.svelte +15 -11
  84. package/dist/progress/progress.svelte +1 -0
  85. package/dist/radio-group/radio-group-item-input.svelte +17 -2
  86. package/dist/range-calendar/range-calendar-root.svelte +13 -19
  87. package/dist/reveal/reveal.svelte +1 -1
  88. package/dist/select/select-content.svelte +1 -0
  89. package/dist/select/select-item.svelte +9 -0
  90. package/dist/select/select-trigger-button.svelte +18 -1
  91. package/dist/skeleton/skeleton.svelte +2 -0
  92. package/dist/slider/slider-input.svelte +1 -0
  93. package/dist/text/index.d.ts +1 -0
  94. package/dist/text/text.svelte +4 -1
  95. package/dist/text/text.svelte.d.ts +1 -0
  96. package/dist/theme-toggle/index.d.ts +18 -0
  97. package/dist/theme-toggle/index.js +3 -0
  98. package/dist/theme-toggle/theme-controller.svelte.d.ts +54 -0
  99. package/dist/theme-toggle/theme-controller.svelte.js +121 -0
  100. package/dist/theme-toggle/theme-flash.d.ts +16 -0
  101. package/dist/theme-toggle/theme-flash.js +38 -0
  102. package/dist/theme-toggle/theme-toggle.svelte +199 -0
  103. package/dist/theme-toggle/theme-toggle.svelte.d.ts +40 -0
  104. package/dist/themes/dark.css +6 -0
  105. package/dist/themes/default.css +92 -0
  106. package/dist/toast/toast-provider.svelte +1 -0
  107. package/dist/toast/toast-root.svelte +1 -0
  108. package/dist/tooltip/tooltip-content.svelte +13 -10
  109. package/dist/typography/heading.svelte +13 -89
  110. package/dist/typography/heading.svelte.d.ts +3 -8
  111. package/dist/typography/index.d.ts +8 -7
  112. package/dist/typography/text.svelte +12 -84
  113. package/dist/typography/text.svelte.d.ts +3 -10
  114. package/dist/video-embed/video-embed-button.svelte +2 -1
  115. package/package.json +7 -2
  116. package/skills/dryui/SKILL.md +18 -5
  117. package/skills/dryui/rules/composition.md +1 -1
  118. package/skills/dryui/rules/theming.md +1 -2
@@ -46,6 +46,15 @@
46
46
  display: grid;
47
47
  border-bottom: 1px solid var(--dry-color-stroke-weak);
48
48
 
49
+ transition:
50
+ opacity var(--dry-duration-fast) var(--dry-ease-out),
51
+ transform var(--dry-duration-fast) var(--dry-ease-out);
52
+
53
+ @starting-style {
54
+ opacity: 0;
55
+ transform: translateY(4px);
56
+ }
57
+
49
58
  &:last-child {
50
59
  border-bottom: none;
51
60
  }
@@ -47,7 +47,7 @@
47
47
  });
48
48
  </script>
49
49
 
50
- <div data-accordion data-orientation={orientation} class={className} {...rest}>
50
+ <div data-accordion data-dry-stagger data-orientation={orientation} class={className} {...rest}>
51
51
  {@render children()}
52
52
  </div>
53
53
 
@@ -83,6 +83,7 @@
83
83
  var(--dry-alert-padding, var(--dry-space-6))
84
84
  )
85
85
  );
86
+ --dry-btn-radius: var(--dry-radius-nested);
86
87
 
87
88
  container-type: inline-size;
88
89
  display: grid;
@@ -121,7 +121,7 @@
121
121
  font-size: var(--dry-avatar-font-size);
122
122
  font-weight: 600;
123
123
  line-height: 1;
124
- border: 1px solid var(--dry-color-stroke-weak);
124
+ box-shadow: inset 0 0 0 1px var(--dry-image-edge);
125
125
  overflow: hidden;
126
126
  user-select: none;
127
127
  }
@@ -177,6 +177,7 @@
177
177
  text-decoration: none;
178
178
  white-space: nowrap;
179
179
  user-select: none;
180
+ transform-origin: center;
180
181
  transition:
181
182
  background var(--dry-duration-fast) var(--dry-ease-default),
182
183
  border-color var(--dry-duration-fast) var(--dry-ease-default),
@@ -191,7 +192,7 @@
191
192
  }
192
193
 
193
194
  &:active:not([data-disabled]) {
194
- transform: translateY(1px);
195
+ transform: scale(0.98);
195
196
  }
196
197
 
197
198
  &[data-disabled] {
@@ -499,7 +500,7 @@
499
500
  --_dry-btn-padding-x: var(--dry-btn-padding-x, 0);
500
501
  --_dry-btn-padding-y: var(--dry-btn-padding-y, 0);
501
502
  aspect-ratio: 1;
502
- height: var(--dry-space-8);
503
+ height: var(--dry-space-10);
503
504
  --_dry-btn-radius: var(--dry-btn-radius, var(--dry-radius-sm));
504
505
  --_dry-btn-font-size: var(
505
506
  --dry-btn-font-size,
@@ -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>
@@ -69,6 +69,7 @@
69
69
  0px,
70
70
  calc(var(--dry-card-radius) - var(--dry-card-padding, var(--dry-space-8)))
71
71
  );
72
+ --dry-btn-radius: var(--dry-radius-nested);
72
73
 
73
74
  container-type: inline-size;
74
75
  display: grid;
@@ -45,6 +45,7 @@
45
45
  font-size="11"
46
46
  fill="currentColor"
47
47
  opacity="0.6"
48
+ data-chart-tick-label
48
49
  >
49
50
  {Math.round(tick.value)}
50
51
  </text>
@@ -63,4 +64,8 @@
63
64
  [data-chart-axis] {
64
65
  color: var(--dry-chart-axis-color);
65
66
  }
67
+
68
+ [data-chart-tick-label] {
69
+ font-variant-numeric: tabular-nums;
70
+ }
66
71
  </style>
@@ -133,8 +133,21 @@
133
133
 
134
134
  &::after {
135
135
  content: '';
136
- display: none;
137
- transition: transform var(--dry-duration-fast) var(--dry-ease-spring);
136
+ display: block;
137
+ height: 60%;
138
+ aspect-ratio: 35 / 60;
139
+ border: solid transparent;
140
+ border-width: 0 2px 2px 0;
141
+ margin-left: calc(var(--dry-checkbox-size) * 0.52);
142
+ margin-top: calc(var(--dry-checkbox-size) * -0.18);
143
+ transform-origin: center;
144
+ opacity: 0;
145
+ transform: rotate(45deg) scale(0.25);
146
+ filter: blur(4px);
147
+ transition:
148
+ opacity var(--dry-duration-fast) var(--dry-ease-spring-snappy),
149
+ transform var(--dry-duration-fast) var(--dry-ease-spring-snappy),
150
+ filter var(--dry-duration-fast) var(--dry-ease-spring-snappy);
138
151
  }
139
152
 
140
153
  &[data-state='checked'] {
@@ -143,16 +156,10 @@
143
156
  box-shadow: inset 0 0 0 1px var(--dry-color-stroke-selected);
144
157
 
145
158
  &::after {
146
- display: block;
147
- height: 60%;
148
- aspect-ratio: 35 / 60;
149
- border: solid var(--dry-checkbox-check-color);
150
- border-width: 0 2px 2px 0;
151
- margin-left: calc(var(--dry-checkbox-size) * 0.52);
152
- margin-top: calc(var(--dry-checkbox-size) * -0.18);
159
+ border-color: var(--dry-checkbox-check-color);
160
+ opacity: 1;
153
161
  transform: rotate(45deg) scale(1);
154
- transform-origin: center;
155
- animation: checkScale var(--dry-duration-fast) var(--dry-ease-spring);
162
+ filter: blur(0);
156
163
  }
157
164
  }
158
165
 
@@ -163,11 +170,15 @@
163
170
  box-shadow: inset 0 0 0 1px var(--dry-color-stroke-selected);
164
171
 
165
172
  &::after {
166
- display: block;
167
173
  height: 2px;
174
+ aspect-ratio: auto;
175
+ border: none;
168
176
  background: var(--dry-checkbox-check-color);
177
+ margin-left: 0;
178
+ margin-top: 0;
179
+ opacity: 1;
169
180
  transform: scale(1);
170
- animation: dashScale var(--dry-duration-fast) var(--dry-ease-spring);
181
+ filter: blur(0);
171
182
  }
172
183
  }
173
184
 
@@ -226,28 +237,16 @@
226
237
  --dry-checkbox-radius: var(--dry-radius-md);
227
238
  }
228
239
 
229
- @keyframes checkScale {
230
- from {
231
- transform: rotate(45deg) scale(0);
240
+ @media (prefers-reduced-motion: reduce) {
241
+ input::after {
242
+ transition: none;
243
+ filter: none;
232
244
  }
233
- to {
245
+ input[data-state='checked']::after {
234
246
  transform: rotate(45deg) scale(1);
235
247
  }
236
- }
237
-
238
- @keyframes dashScale {
239
- from {
240
- transform: scale(0);
241
- }
242
- to {
243
- transform: scale(1);
244
- }
245
- }
246
-
247
- @media (prefers-reduced-motion: reduce) {
248
- input[data-state='checked']::after,
249
248
  input[data-state='indeterminate']::after {
250
- animation: none;
249
+ transform: scale(1);
251
250
  }
252
251
  }
253
252
  </style>
@@ -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');
@@ -64,6 +64,7 @@
64
64
  id={ctx.contentId}
65
65
  aria-labelledby={ctx.inputId}
66
66
  data-combobox-content
67
+ data-dry-stagger
67
68
  data-state={ctx.open ? 'open' : 'closed'}
68
69
  class={className}
69
70
  {...rest}
@@ -97,6 +97,15 @@
97
97
  outline: none;
98
98
  color: var(--dry-color-text-strong);
99
99
  min-height: var(--dry-space-10);
100
+
101
+ transition:
102
+ opacity var(--dry-duration-fast) var(--dry-ease-out),
103
+ transform var(--dry-duration-fast) var(--dry-ease-out);
104
+
105
+ @starting-style {
106
+ opacity: 0;
107
+ transform: translateY(4px);
108
+ }
100
109
  }
101
110
 
102
111
  [data-combobox-item][data-has-icon] {
@@ -71,6 +71,15 @@
71
71
  cursor: pointer;
72
72
  color: var(--dry-color-text-strong);
73
73
  min-height: var(--dry-space-11);
74
+
75
+ transition:
76
+ opacity var(--dry-duration-fast) var(--dry-ease-out),
77
+ transform var(--dry-duration-fast) var(--dry-ease-out);
78
+
79
+ @starting-style {
80
+ opacity: 0;
81
+ transform: translateY(4px);
82
+ }
74
83
  }
75
84
 
76
85
  [data-command-palette-item]:hover:not([data-disabled]) {
@@ -15,6 +15,7 @@
15
15
  <div
16
16
  role="listbox"
17
17
  data-command-palette-list
18
+ data-dry-stagger
18
19
  id={ctx.listId}
19
20
  aria-label="Commands"
20
21
  class={className}
@@ -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,13 +52,15 @@
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"
49
60
  id={ctx.contentId}
50
61
  aria-labelledby={ctx.triggerId}
51
62
  data-context-menu-content
63
+ data-dry-stagger
52
64
  data-state={ctx.open ? 'open' : 'closed'}
53
65
  class={className}
54
66
  {style}
@@ -69,6 +81,7 @@
69
81
  --dry-menu-radius: var(--dry-radius-lg);
70
82
  --dry-menu-shadow: var(--dry-shadow-lg);
71
83
  --dry-menu-padding: var(--dry-space-1);
84
+ --dry-menu-item-padding: var(--dry-space-2_5) var(--dry-space-2);
72
85
 
73
86
  display: grid;
74
87
  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;