@dryui/ui 0.1.2 → 0.1.4

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 (79) hide show
  1. package/dist/accordion/accordion-trigger.svelte +5 -6
  2. package/dist/alert-dialog/alert-dialog-action.svelte +42 -6
  3. package/dist/alert-dialog/alert-dialog-cancel.svelte +44 -5
  4. package/dist/alert-dialog/alert-dialog-footer.svelte +3 -2
  5. package/dist/aurora/aurora.svelte.d.ts +6 -0
  6. package/dist/beam/beam.svelte +17 -10
  7. package/dist/button/button.svelte +51 -1
  8. package/dist/button-group/button-group.svelte +7 -62
  9. package/dist/button-group/context.svelte.d.ts +5 -0
  10. package/dist/button-group/context.svelte.js +11 -0
  11. package/dist/chromatic-aberration/chromatic-aberration.svelte +60 -18
  12. package/dist/collapsible/collapsible-trigger.svelte +4 -7
  13. package/dist/color-picker/color-picker-eyedropper.svelte +4 -11
  14. package/dist/combobox/combobox-group.svelte +1 -1
  15. package/dist/data-grid/data-grid-pagination.svelte +20 -2
  16. package/dist/data-grid/data-grid-root.svelte +1 -0
  17. package/dist/date-field/date-field-root.svelte +66 -20
  18. package/dist/date-field/date-field-segment.svelte +11 -9
  19. package/dist/date-field/date-field-separator.svelte +9 -1
  20. package/dist/date-picker/datepicker-calendar.svelte +168 -13
  21. package/dist/date-picker/datepicker-trigger.svelte +3 -8
  22. package/dist/date-range-picker/date-range-picker-calendar.svelte +177 -13
  23. package/dist/date-range-picker/date-range-picker-root.svelte +0 -6
  24. package/dist/date-range-picker/date-range-picker-trigger.svelte +18 -12
  25. package/dist/dialog/dialog-content.svelte +1 -0
  26. package/dist/field/field-root.svelte +0 -1
  27. package/dist/file-select/file-select-clear.svelte +2 -8
  28. package/dist/file-upload/file-upload-item-delete.svelte +4 -7
  29. package/dist/file-upload/file-upload-root.svelte +0 -4
  30. package/dist/flip-card/flip-card-back.svelte +2 -2
  31. package/dist/flip-card/flip-card-front.svelte +2 -2
  32. package/dist/flip-card/flip-card-root.svelte +2 -0
  33. package/dist/float-button/float-button-root.svelte +2 -1
  34. package/dist/image-comparison/image-comparison.svelte +16 -24
  35. package/dist/input-group/input-group-action.svelte +5 -0
  36. package/dist/input-group/input-group-input.svelte +7 -2
  37. package/dist/input-group/input-group-prefix.svelte +5 -0
  38. package/dist/input-group/input-group-root.svelte +10 -2
  39. package/dist/input-group/input-group-select.svelte +5 -0
  40. package/dist/input-group/input-group-separator.svelte +10 -0
  41. package/dist/input-group/input-group-suffix.svelte +5 -0
  42. package/dist/list/list-item-icon.svelte +8 -0
  43. package/dist/list/list-item-text.svelte +19 -0
  44. package/dist/list/list-item.svelte +42 -0
  45. package/dist/list/list-root.svelte +0 -71
  46. package/dist/list/list-subheader.svelte +11 -0
  47. package/dist/map/map-marker.svelte +10 -0
  48. package/dist/map/map-popup.svelte +7 -0
  49. package/dist/map/map-root.svelte +0 -30
  50. package/dist/multi-select-combobox/multi-select-combobox-group.svelte +1 -1
  51. package/dist/option-swatch-group/option-swatch-group-item.svelte +46 -0
  52. package/dist/option-swatch-group/option-swatch-group-label.svelte +10 -0
  53. package/dist/option-swatch-group/option-swatch-group-meta.svelte +10 -0
  54. package/dist/option-swatch-group/option-swatch-group-root.svelte +0 -79
  55. package/dist/option-swatch-group/option-swatch-group-swatch.svelte +25 -6
  56. package/dist/pin-input/pin-input-cell.svelte +4 -1
  57. package/dist/radio-group/radio-group-item.svelte +90 -0
  58. package/dist/radio-group/radio-group.svelte +0 -89
  59. package/dist/range-calendar/range-calendar-grid.svelte +217 -179
  60. package/dist/range-calendar/range-calendar-root.svelte +24 -10
  61. package/dist/rich-text-editor/rich-text-editor-content.svelte +91 -3
  62. package/dist/rich-text-editor/rich-text-editor-root.svelte +168 -3
  63. package/dist/rich-text-editor/rich-text-editor-toolbar.svelte +318 -275
  64. package/dist/select/select-trigger.svelte +5 -8
  65. package/dist/shader-canvas/shader-canvas.svelte +0 -3
  66. package/dist/sidebar/sidebar-trigger.svelte +3 -2
  67. package/dist/system-map/system-map.svelte +120 -674
  68. package/dist/tabs/tabs-trigger.svelte +7 -4
  69. package/dist/tags-input/tags-input-input.svelte +3 -0
  70. package/dist/tags-input/tags-input-root.svelte +4 -13
  71. package/dist/tags-input/tags-input-tag.svelte +3 -0
  72. package/dist/themes/dark.css +6 -0
  73. package/dist/themes/default.css +3 -0
  74. package/dist/toast/toast-action.svelte +1 -0
  75. package/dist/toast/toast-close.svelte +4 -0
  76. package/dist/toast/toast-provider.svelte +5 -26
  77. package/dist/toast/toast-root.svelte +5 -10
  78. package/dist/virtual-list/virtual-list.svelte +187 -3
  79. package/package.json +3 -3
@@ -8,6 +8,13 @@
8
8
  isDateInRange,
9
9
  formatDate
10
10
  } from '@dryui/primitives';
11
+ import {
12
+ generateWeekdayLabels,
13
+ splitIntoWeeks,
14
+ getDayISOString,
15
+ handleCalendarKeydown,
16
+ focusCalendarDay
17
+ } from '../internal/calendar-grid-utils.js';
11
18
 
12
19
  interface Props extends HTMLAttributes<HTMLDivElement> {}
13
20
 
@@ -15,7 +22,7 @@
15
22
 
16
23
  const ctx = getRangeCalendarCtx();
17
24
 
18
- let gridEl = $state<HTMLDivElement>();
25
+ let gridEl = $state<HTMLDivElement | undefined>();
19
26
 
20
27
  const weekdayLabels = $derived(generateWeekdayLabels(ctx.locale, ctx.weekStartDay));
21
28
 
@@ -30,28 +37,6 @@
30
37
 
31
38
  const weeks = $derived(splitIntoWeeks(calendarDays));
32
39
 
33
- function generateWeekdayLabels(locale: string, weekStartDay: number): string[] {
34
- const formatter = new Intl.DateTimeFormat(locale, { weekday: 'short' });
35
- const labels: string[] = [];
36
- for (let i = 0; i < 7; i++) {
37
- const d = new Date(2024, 0, 7 + ((i + weekStartDay) % 7));
38
- labels.push(formatter.format(d));
39
- }
40
- return labels;
41
- }
42
-
43
- function splitIntoWeeks(days: Date[]): Date[][] {
44
- const result: Date[][] = [];
45
- for (let i = 0; i < days.length; i += 7) {
46
- result.push(days.slice(i, i + 7));
47
- }
48
- return result;
49
- }
50
-
51
- function getDayISOString(day: Date): string {
52
- return `${day.getFullYear()}-${String(day.getMonth() + 1).padStart(2, '0')}-${String(day.getDate()).padStart(2, '0')}`;
53
- }
54
-
55
40
  function isInRange(day: Date): boolean {
56
41
  const start = ctx.startDate;
57
42
  const end = ctx.endDate ?? ctx.hoveredDate;
@@ -83,72 +68,71 @@
83
68
  }
84
69
 
85
70
  function handleDayKeydown(e: KeyboardEvent, day: Date) {
86
- const { key } = e;
87
- let newDate: Date | null = null;
88
-
89
- if (key === 'ArrowLeft') {
90
- e.preventDefault();
91
- newDate = new Date(day);
92
- newDate.setDate(newDate.getDate() - 1);
93
- } else if (key === 'ArrowRight') {
94
- e.preventDefault();
95
- newDate = new Date(day);
96
- newDate.setDate(newDate.getDate() + 1);
97
- } else if (key === 'ArrowUp') {
98
- e.preventDefault();
99
- newDate = new Date(day);
100
- newDate.setDate(newDate.getDate() - 7);
101
- } else if (key === 'ArrowDown') {
102
- e.preventDefault();
103
- newDate = new Date(day);
104
- newDate.setDate(newDate.getDate() + 7);
105
- } else if (key === 'Enter' || key === ' ') {
106
- e.preventDefault();
107
- if (isDateInRange(day, ctx.min, ctx.max)) ctx.selectDate(day);
108
- return;
109
- } else return;
110
-
111
- if (newDate) {
112
- ctx.setFocusedDate(newDate);
113
- requestAnimationFrame(() => {
114
- const isoStr = getDayISOString(newDate!);
115
- const btn = gridEl?.querySelector(`[data-calendar-day="${isoStr}"]`) as HTMLButtonElement;
116
- btn?.focus();
117
- });
71
+ const result = handleCalendarKeydown(e, day, {
72
+ onSelect: (date) => {
73
+ if (isDateInRange(date, ctx.min, ctx.max)) {
74
+ ctx.selectDate(date);
75
+ }
76
+ },
77
+ onEscape: () => {},
78
+ setFocusedDate: (date) => ctx.setFocusedDate(date),
79
+ weekStartDay: ctx.weekStartDay,
80
+ min: ctx.min,
81
+ max: ctx.max
82
+ });
83
+ if (result?.type === 'navigate') {
84
+ requestAnimationFrame(() => focusCalendarDay(gridEl, result.newDate));
118
85
  }
119
86
  }
120
87
 
121
88
  function isDayDisabled(day: Date): boolean {
122
89
  return !isDateInRange(day, ctx.min, ctx.max);
123
90
  }
91
+
92
+ function captureGrid(node: HTMLDivElement) {
93
+ gridEl = node;
94
+
95
+ return {
96
+ destroy() {
97
+ if (gridEl === node) {
98
+ gridEl = undefined;
99
+ }
100
+ }
101
+ };
102
+ }
124
103
  </script>
125
104
 
126
- <div bind:this={gridEl} data-range-calendar-grid class={className} {...rest}>
127
- <div role="group" aria-label={monthYearLabel}>
105
+ <div {@attach captureGrid} data-range-calendar-grid class={className} {...rest}>
106
+ <div data-calendar-panel role="group" aria-label={monthYearLabel}>
128
107
  <div data-calendar-header>
129
- <button type="button" aria-label="Previous month" onclick={() => ctx.prevMonth()}>
108
+ <button
109
+ type="button"
110
+ data-calendar-nav
111
+ aria-label="Previous month"
112
+ onclick={() => ctx.prevMonth()}
113
+ >
130
114
  &#8249;
131
115
  </button>
132
- <span aria-live="polite" aria-atomic="true">
116
+ <span data-calendar-heading aria-live="polite" aria-atomic="true">
133
117
  {monthYearLabel}
134
118
  </span>
135
- <button type="button" aria-label="Next month" onclick={() => ctx.nextMonth()}>
119
+ <button type="button" data-calendar-nav aria-label="Next month" onclick={() => ctx.nextMonth()}>
136
120
  &#8250;
137
121
  </button>
138
122
  </div>
139
123
 
140
124
  <div role="grid" aria-label={monthYearLabel}>
141
- <div role="row">
142
- {#each weekdayLabels as label}
143
- <div role="columnheader" aria-label={label}>
125
+ <div data-calendar-row role="row">
126
+ {#each weekdayLabels as label (label)}
127
+ <div data-calendar-columnheader role="columnheader" aria-label={label}>
144
128
  <span aria-hidden="true">{label}</span>
145
129
  </div>
146
130
  {/each}
147
131
  </div>
148
132
 
149
- {#each weeks as week}
150
- <div role="row">
151
- {#each week as day}
133
+ {#each weeks as week (week[0]?.getTime() ?? 0)}
134
+ <div data-calendar-row role="row">
135
+ {#each week as day (day.getTime())}
152
136
  {@const isCurrent = day.getMonth() === ctx.viewMonth}
153
137
  {@const inRange = isInRange(day)}
154
138
  {@const rangeStart = isRangeStart(day)}
@@ -157,16 +141,23 @@
157
141
  {@const disabled = isDayDisabled(day)}
158
142
  {@const focused = isSameDay(day, ctx.focusedDate)}
159
143
  {@const isoStr = getDayISOString(day)}
160
- <div>
144
+ <div
145
+ data-calendar-cell
146
+ data-in-range={inRange ? '' : undefined}
147
+ data-range-start={rangeStart ? '' : undefined}
148
+ data-range-end={rangeEnd ? '' : undefined}
149
+ >
161
150
  <button
162
151
  role="gridcell"
163
152
  type="button"
153
+ data-calendar-day-button
164
154
  tabindex={focused ? 0 : -1}
165
155
  aria-label={formatDate(day, ctx.locale, {
166
156
  year: 'numeric',
167
157
  month: 'long',
168
158
  day: 'numeric'
169
159
  })}
160
+ aria-current={today ? 'date' : undefined}
170
161
  aria-selected={inRange || undefined}
171
162
  aria-disabled={disabled}
172
163
  data-calendar-day={isoStr}
@@ -195,137 +186,184 @@
195
186
  [data-range-calendar-grid] {
196
187
  display: grid;
197
188
  gap: var(--dry-space-2);
189
+ user-select: none;
190
+ color: var(--dry-color-text-strong);
191
+ font-family: var(--dry-font-sans);
192
+ }
198
193
 
199
- [role='group'] {
200
- display: grid;
201
- gap: var(--dry-space-2);
202
- }
194
+ [data-range-calendar-grid] [data-calendar-panel] {
195
+ display: grid;
196
+ gap: var(--dry-space-3);
197
+ padding: var(--dry-space-3);
198
+ background: var(--dry-range-calendar-panel-bg, var(--dry-color-bg-overlay));
199
+ border: 1px solid var(--dry-range-calendar-panel-border, var(--dry-color-stroke-weak));
200
+ border-radius: calc(var(--dry-range-calendar-radius, var(--dry-radius-lg)) - var(--dry-space-1));
201
+ }
203
202
 
204
- [role='group'] > div:first-child {
205
- display: grid;
206
- grid-template-columns: auto 1fr auto;
207
- align-items: center;
208
- gap: var(--dry-space-2);
209
- }
203
+ [data-range-calendar-grid] [data-calendar-header] {
204
+ display: grid;
205
+ grid-template-columns: auto 1fr auto;
206
+ align-items: center;
207
+ gap: var(--dry-space-2);
208
+ padding-block-end: var(--dry-space-1);
209
+ border-block-end: 1px solid
210
+ color-mix(
211
+ in srgb,
212
+ var(--dry-range-calendar-panel-border, var(--dry-color-stroke-weak)) 70%,
213
+ transparent
214
+ );
215
+ }
210
216
 
211
- [role='group'] > div:first-child button {
212
- display: inline-grid;
213
- place-items: center;
214
- height: var(--dry-space-8);
215
- aspect-ratio: 1;
216
- border: 1px solid transparent;
217
- border-radius: var(--dry-range-calendar-day-radius);
218
- background: transparent;
219
- color: var(--dry-color-text-strong);
220
- cursor: pointer;
221
- transition:
222
- background var(--dry-duration-fast) var(--dry-ease-default),
223
- border-color var(--dry-duration-fast) var(--dry-ease-default);
224
- }
217
+ [data-range-calendar-grid] [data-calendar-nav] {
218
+ display: inline-grid;
219
+ place-items: center;
220
+ height: var(--dry-space-8);
221
+ aspect-ratio: 1;
222
+ border: 1px solid var(--dry-range-calendar-nav-border, var(--dry-color-stroke-weak));
223
+ border-radius: var(--dry-range-calendar-day-radius, var(--dry-radius-md));
224
+ background: var(--dry-range-calendar-nav-bg, var(--dry-color-bg-raised));
225
+ color: var(--dry-range-calendar-nav-color, var(--dry-color-text-weak));
226
+ cursor: pointer;
227
+ transition:
228
+ background var(--dry-duration-fast) var(--dry-ease-default),
229
+ border-color var(--dry-duration-fast) var(--dry-ease-default),
230
+ color var(--dry-duration-fast) var(--dry-ease-default);
231
+ }
225
232
 
226
- [role='group'] > div:first-child button:hover:not([disabled]) {
227
- background: var(--dry-range-calendar-day-hover-bg);
228
- border-color: var(--dry-color-stroke-strong);
229
- }
233
+ [data-range-calendar-grid] [data-calendar-nav]:hover:not([disabled]) {
234
+ background: var(--dry-range-calendar-day-hover-bg, var(--dry-color-fill-hover));
235
+ border-color: var(--dry-color-stroke-strong);
236
+ color: var(--dry-color-text-strong);
237
+ }
230
238
 
231
- [role='group'] > div:first-child button:focus-visible {
232
- outline: 2px solid var(--dry-color-focus-ring);
233
- outline-offset: 1px;
234
- }
239
+ [data-range-calendar-grid] [data-calendar-nav]:focus-visible {
240
+ outline: 2px solid var(--dry-color-focus-ring);
241
+ outline-offset: 1px;
242
+ }
235
243
 
236
- [role='group'] > div:first-child span {
237
- font-size: var(--dry-type-small-size, var(--dry-type-small-size));
238
- font-weight: 600;
239
- letter-spacing: -0.01em;
240
- }
244
+ [data-range-calendar-grid] [data-calendar-heading] {
245
+ font-size: var(--dry-type-small-size, var(--dry-type-small-size));
246
+ font-weight: 700;
247
+ letter-spacing: -0.02em;
248
+ color: var(--dry-range-calendar-heading-color, var(--dry-color-text-strong));
249
+ }
241
250
 
242
- [role='grid'] {
243
- display: grid;
244
- gap: 1px;
245
- }
251
+ [data-range-calendar-grid] [role='grid'] {
252
+ display: grid;
253
+ gap: var(--dry-space-1);
254
+ }
246
255
 
247
- [role='row'] {
248
- display: grid;
249
- grid-template-columns: repeat(7, minmax(0, 1fr));
250
- gap: 1px;
251
- }
256
+ [data-range-calendar-grid] [data-calendar-row] {
257
+ display: grid;
258
+ grid-template-columns: repeat(7, minmax(0, 1fr));
259
+ gap: var(--dry-space-1);
260
+ }
252
261
 
253
- [role='columnheader'] {
254
- display: grid;
255
- place-items: center;
256
- min-height: var(--dry-space-8);
257
- font-size: var(--dry-type-tiny-size, var(--dry-type-tiny-size));
258
- color: var(--dry-range-calendar-outside-color);
259
- text-transform: uppercase;
260
- letter-spacing: 0.04em;
261
- }
262
+ [data-range-calendar-grid] [data-calendar-columnheader] {
263
+ display: grid;
264
+ place-items: center;
265
+ min-height: var(--dry-space-7);
266
+ font-size: calc(var(--dry-type-tiny-size, var(--dry-type-tiny-size)) * 0.92);
267
+ color: var(--dry-range-calendar-outside-color, var(--dry-color-text-weak));
268
+ text-transform: uppercase;
269
+ letter-spacing: 0.08em;
270
+ }
262
271
 
263
- [role='gridcell'] {
264
- display: grid;
265
- place-items: center;
266
- }
272
+ [data-range-calendar-grid] [data-calendar-cell] {
273
+ display: grid;
274
+ grid-template-columns: var(--dry-range-calendar-day-size, 2.5rem);
275
+ place-items: center;
276
+ padding-block: var(--dry-space-0_5);
277
+ border-radius: 0;
278
+ }
267
279
 
268
- [role='gridcell'] button {
269
- display: inline-grid;
270
- place-items: center;
271
- min-height: var(--dry-range-calendar-day-size);
272
- aspect-ratio: 1;
273
- border: none;
274
- border-radius: var(--dry-range-calendar-day-radius);
275
- background: transparent;
276
- color: var(--dry-color-text-strong);
277
- font: inherit;
278
- font-size: var(--dry-type-small-size, var(--dry-type-small-size));
279
- font-variant-numeric: tabular-nums;
280
- cursor: pointer;
281
- transition:
282
- background var(--dry-duration-fast) var(--dry-ease-default),
283
- color var(--dry-duration-fast) var(--dry-ease-default);
284
- }
280
+ [data-range-calendar-grid] [data-calendar-cell][data-in-range] {
281
+ background: var(
282
+ --dry-range-calendar-range-bg,
283
+ color-mix(in srgb, var(--dry-color-fill-brand) 14%, transparent)
284
+ );
285
+ }
285
286
 
286
- [role='gridcell'] button:hover:not([data-disabled]) {
287
- background: var(--dry-range-calendar-day-hover-bg);
288
- }
287
+ [data-range-calendar-grid] [data-calendar-cell][data-range-start][data-in-range] {
288
+ border-start-start-radius: var(--dry-range-calendar-day-radius, var(--dry-radius-md));
289
+ border-end-start-radius: var(--dry-range-calendar-day-radius, var(--dry-radius-md));
290
+ }
289
291
 
290
- [role='gridcell'] button:focus-visible {
291
- outline: 2px solid var(--dry-color-focus-ring);
292
- outline-offset: 1px;
293
- }
292
+ [data-range-calendar-grid] [data-calendar-cell][data-range-end][data-in-range] {
293
+ border-start-end-radius: var(--dry-range-calendar-day-radius, var(--dry-radius-md));
294
+ border-end-end-radius: var(--dry-range-calendar-day-radius, var(--dry-radius-md));
295
+ }
294
296
 
295
- [role='gridcell'] button[data-today]:not([data-selected]) {
296
- color: var(--dry-range-calendar-today-color);
297
- font-weight: 600;
298
- }
297
+ [data-range-calendar-grid] [data-calendar-cell][data-range-start][data-range-end] {
298
+ border-radius: var(--dry-range-calendar-day-radius, var(--dry-radius-md));
299
+ }
299
300
 
300
- [role='gridcell'] button[data-selected] {
301
- background: var(--dry-range-calendar-selected-bg);
302
- color: var(--dry-range-calendar-selected-color);
303
- font-weight: 600;
304
- }
301
+ [data-range-calendar-grid] [data-calendar-day-button] {
302
+ display: inline-grid;
303
+ place-items: center;
304
+ min-height: var(--dry-range-calendar-day-size, 2.5rem);
305
+ padding: 0;
306
+ aspect-ratio: 1;
307
+ border: 1px solid transparent;
308
+ border-radius: var(--dry-range-calendar-day-radius, var(--dry-radius-md));
309
+ background: transparent;
310
+ color: var(--dry-color-text-strong);
311
+ font: inherit;
312
+ font-size: var(--dry-type-small-size, var(--dry-type-small-size));
313
+ font-variant-numeric: tabular-nums;
314
+ font-weight: 500;
315
+ cursor: pointer;
316
+ transition:
317
+ background var(--dry-duration-fast) var(--dry-ease-default),
318
+ border-color var(--dry-duration-fast) var(--dry-ease-default),
319
+ color var(--dry-duration-fast) var(--dry-ease-default);
320
+ }
305
321
 
306
- [role='gridcell'] button[data-selected]:hover:not([data-disabled]) {
307
- background: var(--dry-range-calendar-selected-hover-bg);
308
- }
322
+ [data-range-calendar-grid]
323
+ [data-calendar-day-button]:hover:not([data-disabled]):not([data-range-start]):not(
324
+ [data-range-end]
325
+ ) {
326
+ background: var(--dry-range-calendar-day-hover-bg, var(--dry-color-fill-hover));
327
+ }
309
328
 
310
- [role='gridcell'] button[data-in-range] {
311
- background: var(--dry-range-calendar-range-bg);
312
- border-radius: 0;
313
- }
329
+ [data-range-calendar-grid] [data-calendar-day-button]:focus-visible {
330
+ outline: 2px solid var(--dry-color-focus-ring);
331
+ outline-offset: 1px;
332
+ }
314
333
 
315
- [role='gridcell'] button[data-range-start],
316
- [role='gridcell'] button[data-range-end] {
317
- border-radius: var(--dry-range-calendar-day-radius);
318
- }
334
+ [data-range-calendar-grid]
335
+ [data-calendar-day-button][data-today]:not([data-range-start]):not([data-range-end]) {
336
+ border-color: color-mix(
337
+ in srgb,
338
+ var(--dry-range-calendar-today-color, var(--dry-color-text-brand)) 24%,
339
+ transparent
340
+ );
341
+ color: var(--dry-range-calendar-today-color, var(--dry-color-text-brand));
342
+ font-weight: 600;
343
+ }
319
344
 
320
- [role='gridcell'] button[data-outside-month] {
321
- color: var(--dry-range-calendar-outside-color);
322
- opacity: 0.6;
323
- }
345
+ [data-range-calendar-grid] [data-calendar-day-button][data-range-start],
346
+ [data-range-calendar-grid] [data-calendar-day-button][data-range-end] {
347
+ background: var(--dry-range-calendar-selected-bg, var(--dry-color-fill-brand));
348
+ color: var(--dry-range-calendar-selected-color, var(--dry-color-on-brand));
349
+ font-weight: 700;
350
+ }
324
351
 
325
- [role='gridcell'] button[data-disabled] {
326
- cursor: not-allowed;
327
- opacity: 0.4;
328
- pointer-events: none;
329
- }
352
+ [data-range-calendar-grid]
353
+ [data-calendar-day-button][data-range-start]:hover:not([data-disabled]),
354
+ [data-range-calendar-grid]
355
+ [data-calendar-day-button][data-range-end]:hover:not([data-disabled]) {
356
+ background: var(--dry-range-calendar-selected-hover-bg, var(--dry-color-fill-brand-hover));
357
+ }
358
+
359
+ [data-range-calendar-grid] [data-calendar-day-button][data-outside-month] {
360
+ color: var(--dry-range-calendar-outside-color, var(--dry-color-text-weak));
361
+ opacity: 0.55;
362
+ }
363
+
364
+ [data-range-calendar-grid] [data-calendar-day-button][data-disabled] {
365
+ cursor: not-allowed;
366
+ opacity: 0.35;
367
+ pointer-events: none;
330
368
  }
331
369
  </style>
@@ -112,23 +112,39 @@
112
112
 
113
113
  <style>
114
114
  [data-range-calendar-root] {
115
- --dry-range-calendar-bg: var(--dry-color-bg-overlay);
115
+ --dry-range-calendar-bg: var(--dry-color-bg-base);
116
116
  --dry-range-calendar-border: var(--dry-color-stroke-weak);
117
- --dry-range-calendar-radius: var(--dry-radius-lg);
118
- --dry-range-calendar-shadow: var(--dry-shadow-sm);
117
+ --dry-range-calendar-panel-bg: var(--dry-color-bg-overlay);
118
+ --dry-range-calendar-panel-border: color-mix(
119
+ in srgb,
120
+ var(--dry-color-stroke-strong) 18%,
121
+ transparent
122
+ );
123
+ --dry-range-calendar-heading-color: var(--dry-color-text-strong);
124
+ --dry-range-calendar-nav-bg: var(--dry-color-bg-raised);
125
+ --dry-range-calendar-nav-border: var(--dry-color-stroke-weak);
126
+ --dry-range-calendar-nav-color: var(--dry-color-text-weak);
127
+ --dry-range-calendar-radius: var(--dry-radius-xl);
128
+ --dry-range-calendar-shadow: var(--dry-shadow-raised, var(--dry-shadow-sm));
119
129
  --dry-range-calendar-padding: var(--dry-space-3);
120
- --dry-range-calendar-day-size: 2.25rem;
130
+ --dry-range-calendar-day-size: 2.5rem;
121
131
  --dry-range-calendar-day-radius: var(--dry-radius-md);
122
- --dry-range-calendar-day-hover-bg: var(--dry-color-bg-raised);
132
+ --dry-range-calendar-day-hover-bg: var(--dry-color-fill-hover);
123
133
  --dry-range-calendar-selected-bg: var(--dry-color-fill-brand);
124
134
  --dry-range-calendar-selected-color: var(--dry-color-on-brand);
125
135
  --dry-range-calendar-selected-hover-bg: var(--dry-color-fill-brand-hover);
126
- --dry-range-calendar-range-bg: color-mix(in srgb, var(--dry-color-fill-brand) 15%, transparent);
127
- --dry-range-calendar-today-color: var(--dry-color-fill-brand);
136
+ --dry-range-calendar-range-bg: color-mix(
137
+ in srgb,
138
+ var(--dry-color-fill-brand) 14%,
139
+ transparent
140
+ );
141
+ --dry-range-calendar-today-color: var(--dry-color-text-brand);
128
142
  --dry-range-calendar-outside-color: var(--dry-color-text-weak);
129
143
 
130
- display: grid;
144
+ display: inline-grid;
131
145
  box-sizing: border-box;
146
+ justify-items: start;
147
+ gap: var(--dry-space-3);
132
148
  padding: var(--dry-range-calendar-padding);
133
149
  background: var(--dry-range-calendar-bg);
134
150
  border: 1px solid var(--dry-range-calendar-border);
@@ -137,6 +153,4 @@
137
153
  color: var(--dry-color-text-strong);
138
154
  font-family: var(--dry-font-sans);
139
155
  }
140
-
141
-
142
156
  </style>
@@ -1,13 +1,101 @@
1
1
  <script lang="ts">
2
2
  import type { HTMLAttributes } from 'svelte/elements';
3
- import { RichTextEditor as RichTextEditorPrimitive } from '@dryui/primitives/rich-text-editor';
3
+ import { getRichTextEditorCtx } from '@dryui/primitives/rich-text-editor';
4
4
 
5
5
  interface Props extends HTMLAttributes<HTMLDivElement> {}
6
6
 
7
7
  let { ...rest }: Props = $props();
8
+
9
+ const ctx = getRichTextEditorCtx();
10
+
11
+ let contentEl = $state<HTMLDivElement>();
12
+
13
+ // Register the content element with the context
14
+ $effect(() => {
15
+ if (contentEl) {
16
+ ctx.contentEl = contentEl;
17
+ }
18
+ });
19
+
20
+ // Sync value into contenteditable (also clears hint elements on mount)
21
+ $effect(() => {
22
+ if (!contentEl) return;
23
+ const html = ctx.html || '';
24
+ if (contentEl.innerHTML !== html && document.activeElement !== contentEl) {
25
+ contentEl.innerHTML = html;
26
+ }
27
+ });
28
+
29
+ // Listen for selectionchange on document to track formatting state
30
+ $effect(() => {
31
+ if (!contentEl) return;
32
+
33
+ function onSelectionChange() {
34
+ const sel = window.getSelection();
35
+ if (!sel || sel.rangeCount === 0) return;
36
+ if (contentEl!.contains(sel.anchorNode)) {
37
+ ctx.updateState();
38
+ }
39
+ }
40
+
41
+ document.addEventListener('selectionchange', onSelectionChange);
42
+ return () => {
43
+ document.removeEventListener('selectionchange', onSelectionChange);
44
+ };
45
+ });
46
+
47
+ function handleInput() {
48
+ ctx.updateState();
49
+ ctx.syncValue();
50
+ }
51
+
52
+ function handleKeydown(event: KeyboardEvent) {
53
+ if (ctx.readonly) return;
54
+
55
+ const mod = event.metaKey || event.ctrlKey;
56
+ if (!mod) return;
57
+
58
+ switch (event.key.toLowerCase()) {
59
+ case 'b':
60
+ event.preventDefault();
61
+ ctx.toggleBold();
62
+ break;
63
+ case 'i':
64
+ event.preventDefault();
65
+ ctx.toggleItalic();
66
+ break;
67
+ case 'u':
68
+ event.preventDefault();
69
+ ctx.toggleUnderline();
70
+ break;
71
+ case 'k':
72
+ event.preventDefault();
73
+ contentEl?.dispatchEvent(new CustomEvent('rte-link-request', { bubbles: true }));
74
+ break;
75
+ }
76
+ }
8
77
  </script>
9
78
 
10
- <RichTextEditorPrimitive.Content data-part="content" {...rest} />
79
+ <!--
80
+ The elements below (h1–h3, p, ul, ol, li, a) are created dynamically by the
81
+ browser's execCommand API inside the contenteditable div. They are declared
82
+ here so Svelte can verify the scoped CSS selectors that style them. The
83
+ $effect that syncs ctx.html into innerHTML replaces these on mount.
84
+ -->
85
+ <div
86
+ bind:this={contentEl}
87
+ contenteditable={!ctx.readonly}
88
+ role="textbox"
89
+ aria-multiline="true"
90
+ aria-placeholder={ctx.placeholder || undefined}
91
+ data-placeholder={ctx.placeholder || undefined}
92
+ data-part="content"
93
+ data-rte-content
94
+ data-readonly={ctx.readonly || undefined}
95
+ oninput={handleInput}
96
+ onkeydown={handleKeydown}
97
+ {...rest}
98
+ ><h1>.</h1><h2>.</h2><h3>.</h3><p></p><ul><li></li></ul><ol><li></li></ol><a aria-hidden="true" href="https://example.com">.</a></div>
11
99
 
12
100
  <style>
13
101
  [data-part='content'] {
@@ -32,7 +120,7 @@
32
120
  pointer-events: none;
33
121
  }
34
122
 
35
- /* Typography inside the editor content */
123
+ /* Typography for contenteditable elements created by execCommand */
36
124
  [data-part='content'] h1 {
37
125
  font-size: var(--dry-type-heading-2-size, var(--dry-text-2xl-size));
38
126
  font-weight: 700;