@vobs/ui 0.3.0 → 1.1.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 (212) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +60 -0
  3. package/package.json +52 -110
  4. package/src/activity-rail.ts +125 -0
  5. package/src/alert.ts +65 -0
  6. package/src/assets/icons/alert-circle.svg +1 -0
  7. package/src/assets/icons/alert-triangle.svg +1 -0
  8. package/src/assets/icons/alert.d27e24.svg +1 -0
  9. package/src/assets/icons/alert.svg +1 -0
  10. package/src/assets/icons/apple.svg +1 -0
  11. package/src/assets/icons/arrow-down.svg +1 -0
  12. package/src/assets/icons/arrow-expand.svg +1 -0
  13. package/src/assets/icons/arrow-left.svg +1 -0
  14. package/src/assets/icons/arrow-minimize.svg +1 -0
  15. package/src/assets/icons/arrow-right-to-line.svg +1 -0
  16. package/src/assets/icons/arrow-right.svg +1 -0
  17. package/src/assets/icons/arrow-up-right.svg +1 -0
  18. package/src/assets/icons/arrow-up.0c0c0d.svg +1 -0
  19. package/src/assets/icons/arrow-up.svg +1 -0
  20. package/src/assets/icons/at.svg +1 -0
  21. package/src/assets/icons/atom.0c0c0d.svg +1 -0
  22. package/src/assets/icons/atom.svg +1 -0
  23. package/src/assets/icons/bar.svg +1 -0
  24. package/src/assets/icons/bell.svg +1 -0
  25. package/src/assets/icons/box.svg +1 -0
  26. package/src/assets/icons/bug.svg +1 -0
  27. package/src/assets/icons/calendar.svg +1 -0
  28. package/src/assets/icons/check-circle.33c192.svg +1 -0
  29. package/src/assets/icons/check-circle.svg +1 -0
  30. package/src/assets/icons/check.d1d3db.svg +1 -0
  31. package/src/assets/icons/check.svg +1 -0
  32. package/src/assets/icons/chevron-down.svg +1 -0
  33. package/src/assets/icons/chevron-right.svg +1 -0
  34. package/src/assets/icons/chevron-up.svg +1 -0
  35. package/src/assets/icons/circle.svg +1 -0
  36. package/src/assets/icons/clock.svg +1 -0
  37. package/src/assets/icons/cmd.svg +1 -0
  38. package/src/assets/icons/code.svg +1 -0
  39. package/src/assets/icons/columns.svg +1 -0
  40. package/src/assets/icons/copy.svg +1 -0
  41. package/src/assets/icons/cpu.svg +1 -0
  42. package/src/assets/icons/dashboard.0c0c0d.svg +1 -0
  43. package/src/assets/icons/dashboard.svg +1 -0
  44. package/src/assets/icons/dollar.svg +1 -0
  45. package/src/assets/icons/download.svg +1 -0
  46. package/src/assets/icons/edit.svg +1 -0
  47. package/src/assets/icons/extensions.svg +1 -0
  48. package/src/assets/icons/external.svg +1 -0
  49. package/src/assets/icons/eye.svg +1 -0
  50. package/src/assets/icons/file-text.svg +1 -0
  51. package/src/assets/icons/file.svg +1 -0
  52. package/src/assets/icons/files.svg +1 -0
  53. package/src/assets/icons/filter.svg +1 -0
  54. package/src/assets/icons/folder.svg +1 -0
  55. package/src/assets/icons/gear.svg +1 -0
  56. package/src/assets/icons/git.svg +1 -0
  57. package/src/assets/icons/github.svg +1 -0
  58. package/src/assets/icons/globe.svg +1 -0
  59. package/src/assets/icons/grid-2x2.svg +1 -0
  60. package/src/assets/icons/hash.svg +1 -0
  61. package/src/assets/icons/heart.svg +1 -0
  62. package/src/assets/icons/help.svg +1 -0
  63. package/src/assets/icons/home.svg +1 -0
  64. package/src/assets/icons/image.svg +1 -0
  65. package/src/assets/icons/info-circle.svg +1 -0
  66. package/src/assets/icons/info-square.svg +1 -0
  67. package/src/assets/icons/info.387bff.svg +1 -0
  68. package/src/assets/icons/info.svg +1 -0
  69. package/src/assets/icons/key.svg +1 -0
  70. package/src/assets/icons/layers.svg +1 -0
  71. package/src/assets/icons/layout.svg +1 -0
  72. package/src/assets/icons/link.svg +1 -0
  73. package/src/assets/icons/list.svg +1 -0
  74. package/src/assets/icons/lock.svg +1 -0
  75. package/src/assets/icons/log-out.svg +1 -0
  76. package/src/assets/icons/logo.svg +1 -0
  77. package/src/assets/icons/mail.svg +1 -0
  78. package/src/assets/icons/menu.svg +1 -0
  79. package/src/assets/icons/message-circle.svg +1 -0
  80. package/src/assets/icons/message-plus.svg +1 -0
  81. package/src/assets/icons/message-square.svg +1 -0
  82. package/src/assets/icons/mic.svg +1 -0
  83. package/src/assets/icons/minus.svg +1 -0
  84. package/src/assets/icons/moon.svg +1 -0
  85. package/src/assets/icons/more-h.svg +1 -0
  86. package/src/assets/icons/mouse.svg +1 -0
  87. package/src/assets/icons/palette.svg +1 -0
  88. package/src/assets/icons/panel-bottom.svg +1 -0
  89. package/src/assets/icons/pause.svg +1 -0
  90. package/src/assets/icons/play.svg +1 -0
  91. package/src/assets/icons/plug.svg +1 -0
  92. package/src/assets/icons/plus.0c0c0d.svg +1 -0
  93. package/src/assets/icons/plus.svg +1 -0
  94. package/src/assets/icons/refresh.svg +1 -0
  95. package/src/assets/icons/ruler.svg +1 -0
  96. package/src/assets/icons/scroll-text.svg +1 -0
  97. package/src/assets/icons/search-menu.svg +1 -0
  98. package/src/assets/icons/search.svg +1 -0
  99. package/src/assets/icons/send.0c0c0d.svg +1 -0
  100. package/src/assets/icons/send.svg +1 -0
  101. package/src/assets/icons/settings.0c0c0d.svg +1 -0
  102. package/src/assets/icons/settings.svg +1 -0
  103. package/src/assets/icons/shield.svg +1 -0
  104. package/src/assets/icons/sidebar-left.svg +1 -0
  105. package/src/assets/icons/sidebar-right.svg +1 -0
  106. package/src/assets/icons/sliders.svg +1 -0
  107. package/src/assets/icons/sparkles.32f08c.svg +1 -0
  108. package/src/assets/icons/sparkles.svg +1 -0
  109. package/src/assets/icons/square.svg +1 -0
  110. package/src/assets/icons/star.svg +1 -0
  111. package/src/assets/icons/sun.svg +1 -0
  112. package/src/assets/icons/terminal.svg +1 -0
  113. package/src/assets/icons/trash.svg +1 -0
  114. package/src/assets/icons/trending-down.svg +1 -0
  115. package/src/assets/icons/trending-up.svg +1 -0
  116. package/src/assets/icons/type.svg +1 -0
  117. package/src/assets/icons/upload.svg +1 -0
  118. package/src/assets/icons/user-circle.svg +1 -0
  119. package/src/assets/icons/user.svg +1 -0
  120. package/src/assets/icons/users.svg +1 -0
  121. package/src/assets/icons/wrench.svg +1 -0
  122. package/src/assets/icons/x-circle.svg +1 -0
  123. package/src/assets/icons/x.svg +1 -0
  124. package/src/assets/icons/zap.0c0c0d.svg +1 -0
  125. package/src/assets/icons/zap.svg +1 -0
  126. package/src/avatar.ts +64 -0
  127. package/src/button.ts +88 -0
  128. package/src/card.ts +59 -0
  129. package/src/chat-composer.ts +156 -0
  130. package/src/dialog.ts +127 -0
  131. package/src/drawer.ts +131 -0
  132. package/src/editor-tabs.ts +184 -0
  133. package/src/file-tree.ts +214 -0
  134. package/src/forms.ts +270 -0
  135. package/src/icon.ts +118 -0
  136. package/src/icons.js +165 -0
  137. package/src/index.test.ts +213 -0
  138. package/src/index.ts +94 -0
  139. package/src/kbd.ts +72 -0
  140. package/src/menu.ts +119 -0
  141. package/src/nav-list.ts +157 -0
  142. package/src/overlay.test.ts +66 -0
  143. package/src/overlay.ts +205 -0
  144. package/src/page-header.ts +97 -0
  145. package/src/pagination.ts +163 -0
  146. package/src/second-batch.test.ts +205 -0
  147. package/src/stat-card.ts +92 -0
  148. package/src/status-bar.ts +108 -0
  149. package/src/styles/base.css +240 -0
  150. package/src/styles/components.css +1234 -0
  151. package/{dist → src/styles}/styles.css +2 -0
  152. package/src/styles/toast.css +57 -0
  153. package/src/styles/tokens.css +438 -0
  154. package/src/styles/vui.css +47 -0
  155. package/src/table.ts +191 -0
  156. package/src/tabs.ts +100 -0
  157. package/src/tag.ts +33 -0
  158. package/src/third-batch.test.ts +137 -0
  159. package/src/toast.test.ts +55 -0
  160. package/src/toast.ts +79 -0
  161. package/src/types.ts +25 -0
  162. package/src/utils.ts +221 -0
  163. package/src/workbench-titlebar.ts +137 -0
  164. package/dist/app-shell-dom.d.ts +0 -30
  165. package/dist/app-shell-dom.js +0 -913
  166. package/dist/app-shell.d.ts +0 -235
  167. package/dist/app-shell.js +0 -415
  168. package/dist/base.css +0 -51
  169. package/dist/calendar-picker.d.ts +0 -45
  170. package/dist/calendar-picker.js +0 -217
  171. package/dist/calendar.d.ts +0 -95
  172. package/dist/calendar.js +0 -194
  173. package/dist/complex-inputs.d.ts +0 -300
  174. package/dist/complex-inputs.js +0 -866
  175. package/dist/components.css +0 -4756
  176. package/dist/data-display.d.ts +0 -1094
  177. package/dist/data-display.js +0 -2341
  178. package/dist/dialog.d.ts +0 -26
  179. package/dist/dialog.js +0 -94
  180. package/dist/feedback.d.ts +0 -88
  181. package/dist/feedback.js +0 -281
  182. package/dist/forms.d.ts +0 -378
  183. package/dist/forms.js +0 -1019
  184. package/dist/icons-config.d.ts +0 -25
  185. package/dist/icons-config.js +0 -14
  186. package/dist/icons.d.ts +0 -41
  187. package/dist/icons.js +0 -185
  188. package/dist/index.d.ts +0 -31
  189. package/dist/index.js +0 -40
  190. package/dist/locale/en-US.d.ts +0 -31
  191. package/dist/locale/en-US.js +0 -20
  192. package/dist/locale/zh-CN.d.ts +0 -31
  193. package/dist/locale/zh-CN.js +0 -20
  194. package/dist/navigation.d.ts +0 -609
  195. package/dist/navigation.js +0 -1707
  196. package/dist/overlay.d.ts +0 -308
  197. package/dist/overlay.js +0 -974
  198. package/dist/page-state.d.ts +0 -23
  199. package/dist/page-state.js +0 -96
  200. package/dist/primitives.d.ts +0 -268
  201. package/dist/primitives.js +0 -764
  202. package/dist/theme-data.d.ts +0 -394
  203. package/dist/theme-data.js +0 -417
  204. package/dist/theme.d.ts +0 -97
  205. package/dist/theme.js +0 -301
  206. package/dist/tokens.css +0 -570
  207. package/dist/tree.d.ts +0 -144
  208. package/dist/tree.js +0 -475
  209. package/dist/virtual-list.d.ts +0 -190
  210. package/dist/virtual-list.js +0 -538
  211. package/dist/workbench.d.ts +0 -136
  212. package/dist/workbench.js +0 -156
package/dist/base.css DELETED
@@ -1,51 +0,0 @@
1
- @layer vobs.base {
2
- :where(html) {
3
- font-family: var(--font-family-default);
4
- font-size: var(--body-base-font-size);
5
- line-height: var(--body-base-line-height);
6
- background: var(--bg-base-default);
7
- color: var(--text-default);
8
- }
9
-
10
- :where(*, *::before, *::after) {
11
- box-sizing: border-box;
12
- }
13
-
14
- :where(body) {
15
- margin: 0;
16
- background: var(--bg-base-default);
17
- color: var(--text-default);
18
- }
19
-
20
- :where(button, input, select, textarea) {
21
- font: inherit;
22
- }
23
-
24
- :where(.k-icon) {
25
- display: inline-block;
26
- width: 1em;
27
- height: 1em;
28
- flex: none;
29
- color: currentColor;
30
- vertical-align: -0.125em;
31
- }
32
-
33
- :where(:focus-visible) {
34
- outline: 1px solid var(--border-brand);
35
- }
36
-
37
- @media (prefers-reduced-motion: reduce) {
38
- :where(*, *::before, *::after) {
39
- scroll-behavior: auto !important;
40
- transition-duration: 0.01ms !important;
41
- animation-duration: 0.01ms !important;
42
- animation-iteration-count: 1 !important;
43
- }
44
- }
45
-
46
- @media (forced-colors: active) {
47
- :where(:focus-visible) {
48
- outline-color: Highlight;
49
- }
50
- }
51
- }
@@ -1,45 +0,0 @@
1
- /** @license MIT
2
- * Copyright (c) 2026 vobsjs
3
- * @vobs/ui
4
- */
5
- import { type ComponentProps } from '@vobs/runtime-dom';
6
- /**
7
- * Calendar picker template component (recipe layer, scheme A② — wraps plans into a ready-to-use component).
8
- *
9
- * `<k-calendar-picker :value="date" @update="onDate" />`
10
- *
11
- * - Input (readonly, opens on click) + calendar popover (rendered from createCalendarPlans, clicks handled via event delegation).
12
- * - Interaction state machine: open (popover toggle) + displayMonth (displayed month, prev/next rolls across years).
13
- * - Picking a day → emit('update', iso) + close the popover; outside click / Escape closes.
14
- * - SSR: renders only the input (popover is closed by default, client-only interaction).
15
- * - Styles: .kui-calendar-picker / .kui-calendar-* in components.css (same token source).
16
- */
17
- export type CalendarPickerProps = {
18
- /** Selected value 'YYYY-MM-DD' (signal-backed, externally controlled). */
19
- readonly value?: string;
20
- /** Initial displayed month 'YYYY-MM' (default: the month of value, or the current month). */
21
- readonly month?: string;
22
- readonly min?: string;
23
- readonly max?: string;
24
- readonly disabled?: boolean;
25
- readonly locale?: string;
26
- readonly placeholder?: string;
27
- readonly className?: string;
28
- readonly [key: string]: unknown;
29
- };
30
- export interface CalendarPickerEvents extends Record<string, (...args: never[]) => unknown> {
31
- /** Day picked: payload is 'YYYY-MM-DD'. */
32
- update: (...args: readonly [iso: string]) => unknown;
33
- /** Popover opened. */
34
- open: (...args: readonly []) => unknown;
35
- /** Popover closed. */
36
- close: (...args: readonly []) => unknown;
37
- }
38
- export interface CalendarPickerScope {
39
- readonly props: ComponentProps<CalendarPickerProps>;
40
- readonly emit: <Name extends keyof CalendarPickerEvents & string>(name: Name, ...args: Parameters<CalendarPickerEvents[Name]>) => void;
41
- }
42
- /** Component definition: `<k-calendar-picker :value="date" @update="onDate" />`. */
43
- export declare const CalendarPicker: import("@vobs/runtime-core").ComponentDefinition<CalendarPickerProps, CalendarPickerEvents, CalendarPickerScope, Readonly<Record<string, Readonly<Record<string, unknown>>>>>;
44
- /** Server template (SSR): only the input (popover closed by default). */
45
- export declare const CalendarPickerServerTemplate: import("@vobs/server-renderer").ServerTemplate<CalendarPickerScope>;
@@ -1,217 +0,0 @@
1
- /** @license MIT
2
- * Copyright (c) 2026 vobsjs
3
- * @vobs/ui
4
- */
5
- import { effect, signal } from '@vobs/reactivity';
6
- import { createCompiledTemplate, defineComponent, } from '@vobs/runtime-dom';
7
- import { createServerTemplate, escapeHtmlAttribute } from '@vobs/server-renderer';
8
- import { createCalendarPlans } from './calendar.js';
9
- const ROOT_CLASS = 'kui-calendar-picker';
10
- /** Initial displayed month: month prop → value's month → current month. */
11
- function resolveInitialMonth(month, value) {
12
- if (month !== undefined && /^\d{4}-\d{2}$/.test(month))
13
- return month;
14
- const fromValue = value === undefined ? undefined : value.slice(0, 7);
15
- if (fromValue !== undefined && /^\d{4}-\d{2}$/.test(fromValue))
16
- return fromValue;
17
- const now = new Date();
18
- return `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}`;
19
- }
20
- /** Shifts the displayed month (rolls over across years). */
21
- function shiftMonth(current, delta) {
22
- const parts = current.split('-');
23
- const year = Number(parts[0]);
24
- const month = Number(parts[1]);
25
- if (!Number.isFinite(year) || !Number.isFinite(month))
26
- return current;
27
- const shifted = new Date(year, month - 1 + delta, 1);
28
- return `${shifted.getFullYear()}-${String(shifted.getMonth() + 1).padStart(2, '0')}`;
29
- }
30
- /** Renders the popover content: header (prev/title/next) + weekday row + 42 cells. Events are delegated, so no rebinding is needed. */
31
- function renderPopover(popover, scope, displayMonth) {
32
- popover.replaceChildren();
33
- const plans = createCalendarPlans({
34
- id: 'kui-calendar',
35
- ...(scope.props.value?.value === undefined ? {} : { value: scope.props.value.value }),
36
- month: displayMonth,
37
- ...(scope.props.min?.value === undefined ? {} : { min: scope.props.min.value }),
38
- ...(scope.props.max?.value === undefined ? {} : { max: scope.props.max.value }),
39
- ...(scope.props.disabled?.value === undefined ? {} : { disabled: scope.props.disabled.value }),
40
- locale: scope.props.locale?.value ?? 'zh-CN',
41
- });
42
- const header = document.createElement('div');
43
- header.className = plans.header.className;
44
- const prev = document.createElement('button');
45
- prev.className = plans.prevButton.className;
46
- prev.type = 'button';
47
- prev.setAttribute('data-direction', 'prev');
48
- prev.setAttribute('aria-label', 'Previous month');
49
- const title = document.createElement('span');
50
- title.className = plans.title.className;
51
- title.textContent = String(plans.title.attrs['data-label'] ?? '');
52
- const next = document.createElement('button');
53
- next.className = plans.nextButton.className;
54
- next.type = 'button';
55
- next.setAttribute('data-direction', 'next');
56
- next.setAttribute('aria-label', 'Next month');
57
- header.append(prev, title, next);
58
- const weekdayRow = document.createElement('div');
59
- weekdayRow.className = plans.weekdayRow.className;
60
- for (const weekday of plans.weekdays) {
61
- const span = document.createElement('span');
62
- span.className = weekday.className;
63
- span.textContent = String(weekday.attrs['data-label'] ?? '');
64
- weekdayRow.append(span);
65
- }
66
- const grid = document.createElement('div');
67
- grid.className = plans.grid.className;
68
- grid.setAttribute('role', 'grid');
69
- for (const day of plans.days) {
70
- const button = document.createElement('button');
71
- button.className = day.className;
72
- button.type = 'button';
73
- for (const [name, value] of Object.entries(day.attrs)) {
74
- if (value === undefined)
75
- continue;
76
- button.setAttribute(name, typeof value === 'boolean' ? String(value) : String(value));
77
- }
78
- button.textContent = String(day.day);
79
- grid.append(button);
80
- }
81
- popover.append(header, weekdayRow, grid);
82
- }
83
- /** Shared picker assembly for mount/hydrate (input + popover state machine + event delegation + outside-click/Escape close + dispose). */
84
- function mountCalendarPicker(owner, root, scope, _environment) {
85
- const input = document.createElement('input');
86
- input.className = 'kui-calendar-picker__input';
87
- input.type = 'text';
88
- input.readOnly = true;
89
- input.setAttribute('role', 'combobox');
90
- input.setAttribute('aria-haspopup', 'dialog');
91
- input.setAttribute('aria-expanded', 'false');
92
- const popover = document.createElement('div');
93
- popover.className = 'kui-calendar-popover';
94
- popover.setAttribute('role', 'dialog');
95
- popover.hidden = true;
96
- root.append(input, popover);
97
- const open = signal(false);
98
- const displayMonth = signal(resolveInitialMonth(scope.props.month?.value, scope.props.value?.value));
99
- const stopValueSync = effect(() => {
100
- const value = scope.props.value?.value ?? '';
101
- if (input.value !== value)
102
- input.value = value;
103
- });
104
- const stopPlaceholder = effect(() => {
105
- input.placeholder = scope.props.placeholder?.value ?? '';
106
- });
107
- const stopRender = effect(() => {
108
- const isOpen = open.value;
109
- const month = displayMonth.value;
110
- void scope.props.value?.value;
111
- void scope.props.locale?.value;
112
- if (isOpen) {
113
- renderPopover(popover, scope, month);
114
- }
115
- popover.hidden = !isOpen;
116
- input.setAttribute('aria-expanded', String(isOpen));
117
- });
118
- const stopDisabled = effect(() => {
119
- input.disabled = scope.props.disabled?.value === true;
120
- });
121
- const onPopoverClick = (event) => {
122
- const target = event.target;
123
- if (target === null)
124
- return;
125
- const isoDay = target.closest('[data-iso]');
126
- if (isoDay !== null) {
127
- const iso = isoDay.dataset['iso'];
128
- if (iso !== undefined && scope.props.disabled?.value !== true) {
129
- scope.emit('update', iso);
130
- open.value = false;
131
- scope.emit('close');
132
- }
133
- return;
134
- }
135
- const nav = target.closest('[data-direction]');
136
- if (nav !== null) {
137
- const direction = nav.dataset['direction'];
138
- if (direction === 'prev' || direction === 'next') {
139
- displayMonth.value = shiftMonth(displayMonth.value, direction === 'prev' ? -1 : 1);
140
- }
141
- }
142
- };
143
- popover.addEventListener('click', onPopoverClick);
144
- const onInputClick = () => {
145
- if (scope.props.disabled?.value === true)
146
- return;
147
- const next = !open.value;
148
- open.value = next;
149
- scope.emit(next ? 'open' : 'close');
150
- };
151
- input.addEventListener('click', onInputClick);
152
- const onDocumentClick = (event) => {
153
- if (!open.value)
154
- return;
155
- const target = event.target;
156
- if (target !== null && root.contains(target))
157
- return;
158
- open.value = false;
159
- scope.emit('close');
160
- };
161
- document.addEventListener('mousedown', onDocumentClick);
162
- const onKeyDown = (event) => {
163
- if (event.key !== 'Escape' || !open.value)
164
- return;
165
- open.value = false;
166
- scope.emit('close');
167
- };
168
- document.addEventListener('keydown', onKeyDown);
169
- owner.own(() => {
170
- stopValueSync();
171
- stopPlaceholder();
172
- stopRender();
173
- stopDisabled();
174
- popover.removeEventListener('click', onPopoverClick);
175
- input.removeEventListener('click', onInputClick);
176
- document.removeEventListener('mousedown', onDocumentClick);
177
- document.removeEventListener('keydown', onKeyDown);
178
- });
179
- }
180
- const template = createCompiledTemplate({
181
- templateId: 'k-calendar-picker',
182
- sourceId: '@vobs/ui/calendar-picker',
183
- rootTag: 'div',
184
- mount(owner, scope, environment) {
185
- const root = document.createElement('div');
186
- root.className = ROOT_CLASS;
187
- mountCalendarPicker(owner, root, scope, environment);
188
- return { root, refs: {} };
189
- },
190
- hydrate(owner, root, scope, environment) {
191
- if (root.localName !== 'div')
192
- return undefined;
193
- root.classList.add(ROOT_CLASS);
194
- mountCalendarPicker(owner, root, scope, environment);
195
- return { root, refs: {} };
196
- },
197
- });
198
- /** Component definition: `<k-calendar-picker :value="date" @update="onDate" />`. */
199
- export const CalendarPicker = defineComponent({
200
- template,
201
- props: ['value', 'month', 'min', 'max', 'disabled', 'locale', 'placeholder', 'className'],
202
- emits: ['update', 'open', 'close'],
203
- setup(context) {
204
- return { props: context.props, emit: context.emit.bind(context) };
205
- },
206
- });
207
- /** Server template (SSR): only the input (popover closed by default). */
208
- export const CalendarPickerServerTemplate = createServerTemplate({
209
- templateId: 'k-calendar-picker',
210
- sourceId: '@vobs/ui/calendar-picker',
211
- rootTag: 'div',
212
- render(scope) {
213
- const value = scope.props.value?.value ?? '';
214
- const placeholder = scope.props.placeholder?.value ?? '';
215
- return `<div class="${ROOT_CLASS}"><input class="kui-calendar-picker__input" type="text" readonly value="${escapeHtmlAttribute(value)}" placeholder="${escapeHtmlAttribute(placeholder)}" /></div>`;
216
- },
217
- });
@@ -1,95 +0,0 @@
1
- /** @license MIT
2
- * Copyright (c) 2026 vobsjs
3
- * @vobs/ui
4
- */
5
- import { type UiAttributes, type UiDomPlan } from './primitives.js';
6
- /**
7
- * Calendar panel plans (official component, scheme A — implemented inside the ui library, not an adapter).
8
- *
9
- * Month-view grid: header (prev/title/next) + weekday row + 42-cell date grid (6 weeks, including leading/trailing out-of-month cells).
10
- * Homogeneous with other plans components in the ui package (combobox/cascader pattern): static DOM plans + derived state.
11
- * Day cells are dynamic data but expressed as a static plans array (days: readonly CalendarDayPlan[]) — apps render them via templates.
12
- *
13
- * Localization: Intl API (weekday/month names follow locale, default zh-CN; no @vobs/i18n dependency).
14
- * Date semantics: ISO 'YYYY-MM-DD' strings (lexicographic order = chronological order, min/max compared as strings).
15
- * State derivation: disabled > selected > today > outside > idle (data-state applied by createUiDomPlan).
16
- */
17
- export type CalendarDayState = 'idle' | 'today' | 'selected' | 'outside' | 'disabled';
18
- export interface CalendarDayPlan extends UiDomPlan<'button'> {
19
- /** Day of month (1-31). */
20
- readonly day: number;
21
- /** ISO date 'YYYY-MM-DD'. */
22
- readonly iso: string;
23
- /** Whether the day belongs to the displayed month (false = leading/trailing out-of-month cell). */
24
- readonly inMonth: boolean;
25
- /** Derived state (disabled > selected > today > outside > idle). */
26
- readonly dayState: CalendarDayState;
27
- }
28
- export interface CalendarPlanOptions {
29
- /** Root element id (child element ids are derived from it). */
30
- readonly id: string;
31
- /** Selected value 'YYYY-MM-DD' (renders the selected state). */
32
- readonly value?: string;
33
- /** Displayed month 'YYYY-MM' (defaults to the current month). */
34
- readonly month?: string;
35
- /** Minimum selectable date 'YYYY-MM-DD' (inclusive). */
36
- readonly min?: string;
37
- /** Maximum selectable date 'YYYY-MM-DD' (inclusive). */
38
- readonly max?: string;
39
- /** Disables the whole calendar. */
40
- readonly disabled?: boolean;
41
- /** BCP 47 locale (default 'zh-CN'); determines weekday/month names and title format. */
42
- readonly locale?: string;
43
- readonly className?: string;
44
- readonly attrs?: UiAttributes;
45
- }
46
- export interface CalendarPlans {
47
- readonly root: UiDomPlan<'div'>;
48
- readonly header: UiDomPlan<'div'>;
49
- readonly prevButton: UiDomPlan<'button'>;
50
- readonly nextButton: UiDomPlan<'button'>;
51
- readonly title: UiDomPlan<'span'>;
52
- readonly weekdayRow: UiDomPlan<'div'>;
53
- /** 7 weekday abbreviations (Sunday-first, follows locale). */
54
- readonly weekdays: readonly UiDomPlan<'span'>[];
55
- readonly grid: UiDomPlan<'div'>;
56
- /** 42 cells (6 weeks, starting from the Sunday of the displayed month's first day's week). */
57
- readonly days: readonly CalendarDayPlan[];
58
- }
59
- /** Date → ISO 'YYYY-MM-DD' (local timezone). */
60
- export declare function formatCalendarDate(date: Date): string;
61
- /** ISO 'YYYY-MM-DD' → local Date (returns undefined for invalid/overflow input). */
62
- export declare function parseCalendarDate(iso: string): Date | undefined;
63
- /**
64
- * Builds calendar panel plans (42-cell month view).
65
- *
66
- * Layout convention: Sunday-first (Chinese convention) — the grid starts on the Sunday of the week
67
- * containing the displayed month's first day, for 42 cells total.
68
- * Per-cell state derivation: disabled (out of min/max or whole calendar disabled) > selected > today > outside > idle.
69
- */
70
- export declare function createCalendarPlans(options: CalendarPlanOptions): CalendarPlans;
71
- /**
72
- * Date Range Picker plans — two range inputs (start/end) + a dual-calendar popover container.
73
- * Spec source: common enterprise-backend component set (high-frequency list filtering; no UIStyle spec, this is an out-of-spec component).
74
- * Calendar content is rendered by the app into startCalendar / endCalendar using createCalendarPlans.
75
- */
76
- export interface DateRangePickerPlans {
77
- readonly root: UiDomPlan<'div'>;
78
- readonly startInput: UiDomPlan<'input'>;
79
- readonly separator: UiDomPlan<'span'>;
80
- readonly endInput: UiDomPlan<'input'>;
81
- /** Popover container (hidden by default; visibility controlled by the app). */
82
- readonly popover: UiDomPlan<'div'>;
83
- readonly startCalendar: UiDomPlan<'div'>;
84
- readonly endCalendar: UiDomPlan<'div'>;
85
- }
86
- export interface DateRangePickerPlanOptions {
87
- readonly id: string;
88
- readonly startValue?: string;
89
- readonly endValue?: string;
90
- readonly placeholder?: string;
91
- readonly disabled?: boolean;
92
- readonly className?: string;
93
- readonly attrs?: UiAttributes;
94
- }
95
- export declare function createDateRangePickerPlans(options: DateRangePickerPlanOptions): DateRangePickerPlans;
package/dist/calendar.js DELETED
@@ -1,194 +0,0 @@
1
- /** @license MIT
2
- * Copyright (c) 2026 vobsjs
3
- * @vobs/ui
4
- */
5
- import { createUiDomPlan } from './primitives.js';
6
- const pad2 = (value) => value.toString().padStart(2, '0');
7
- /** Date → ISO 'YYYY-MM-DD' (local timezone). */
8
- export function formatCalendarDate(date) {
9
- return `${date.getFullYear()}-${pad2(date.getMonth() + 1)}-${pad2(date.getDate())}`;
10
- }
11
- /** ISO 'YYYY-MM-DD' → local Date (returns undefined for invalid/overflow input). */
12
- export function parseCalendarDate(iso) {
13
- const match = /^(\d{4})-(\d{2})-(\d{2})$/.exec(iso);
14
- if (match === null)
15
- return undefined;
16
- const year = Number(match[1]);
17
- const month = Number(match[2]) - 1;
18
- const day = Number(match[3]);
19
- if (month < 0 || month > 11 || day < 1 || day > 31)
20
- return undefined;
21
- const date = new Date(year, month, day);
22
- // Read-back validation for overflow (e.g. 2026-13-40 is normalized by JS to 2027-02-08)
23
- if (date.getFullYear() !== year || date.getMonth() !== month || date.getDate() !== day) {
24
- return undefined;
25
- }
26
- return date;
27
- }
28
- /** Displayed-month parse: 'YYYY-MM' → { year, month } (invalid input falls back to the current month). */
29
- function parseCalendarMonth(month) {
30
- const match = month === undefined ? null : /^(\d{4})-(\d{2})$/.exec(month);
31
- if (match === null) {
32
- const now = new Date();
33
- return { year: now.getFullYear(), month: now.getMonth() };
34
- }
35
- const parsed = { year: Number(match[1]), month: Number(match[2]) - 1 };
36
- return parsed.month >= 0 && parsed.month <= 11
37
- ? parsed
38
- : { year: parsed.year, month: parsed.month };
39
- }
40
- /** Weekday names (Sunday-first): 2024-01-07 is a Sunday; format the following 7 days via Intl. */
41
- function resolveWeekdayNames(locale) {
42
- const formatter = new Intl.DateTimeFormat(locale, { weekday: 'narrow' });
43
- return Array.from({ length: 7 }, (_, index) => formatter.format(new Date(2024, 0, 7 + index)));
44
- }
45
- /** Month title format follows the locale, e.g. 'August 2026' (en-US). */
46
- function resolveMonthTitle(year, month, locale) {
47
- return new Intl.DateTimeFormat(locale, { year: 'numeric', month: 'long' }).format(new Date(year, month, 1));
48
- }
49
- /**
50
- * Builds calendar panel plans (42-cell month view).
51
- *
52
- * Layout convention: Sunday-first (Chinese convention) — the grid starts on the Sunday of the week
53
- * containing the displayed month's first day, for 42 cells total.
54
- * Per-cell state derivation: disabled (out of min/max or whole calendar disabled) > selected > today > outside > idle.
55
- */
56
- export function createCalendarPlans(options) {
57
- const { id, locale = 'zh-CN' } = options;
58
- const display = parseCalendarMonth(options.month);
59
- const todayIso = formatCalendarDate(new Date());
60
- // Start cell: back up from the first day of the displayed month to the preceding Sunday
61
- const firstOfMonth = new Date(display.year, display.month, 1);
62
- const start = new Date(display.year, display.month, 1 - firstOfMonth.getDay());
63
- const days = [];
64
- for (let index = 0; index < 42; index += 1) {
65
- const date = new Date(start.getFullYear(), start.getMonth(), start.getDate() + index);
66
- const iso = formatCalendarDate(date);
67
- const inMonth = date.getMonth() === display.month;
68
- const isDisabled = options.disabled === true ||
69
- (options.min !== undefined && iso < options.min) ||
70
- (options.max !== undefined && iso > options.max);
71
- const isSelected = !isDisabled && iso === options.value;
72
- const isToday = !isDisabled && !isSelected && iso === todayIso;
73
- const dayState = isDisabled
74
- ? 'disabled'
75
- : isSelected
76
- ? 'selected'
77
- : isToday
78
- ? 'today'
79
- : inMonth
80
- ? 'idle'
81
- : 'outside';
82
- days.push({
83
- ...createUiDomPlan('button', 'calendar-day', {
84
- state: dayState,
85
- disabled: isDisabled,
86
- ...(inMonth ? {} : { className: 'kui-calendar-day--outside' }),
87
- attrs: {
88
- id: `${id}-day-${index}`,
89
- type: 'button',
90
- 'data-iso': iso,
91
- 'data-in-month': inMonth ? 'true' : 'false',
92
- 'aria-label': date.toLocaleDateString(locale),
93
- 'aria-pressed': isSelected ? 'true' : undefined,
94
- 'aria-current': isToday ? 'date' : undefined,
95
- ...options.attrs,
96
- },
97
- }),
98
- day: date.getDate(),
99
- iso,
100
- inMonth,
101
- dayState,
102
- });
103
- }
104
- const weekdays = resolveWeekdayNames(locale).map((name, index) => createUiDomPlan('span', 'calendar-weekday', {
105
- attrs: {
106
- id: `${id}-weekday-${index}`,
107
- 'data-label': name,
108
- },
109
- }));
110
- return {
111
- root: createUiDomPlan('div', 'calendar', {
112
- state: options.disabled === true ? 'disabled' : 'idle',
113
- ...(options.className === undefined ? {} : { className: options.className }),
114
- attrs: { id: `${id}-calendar`, ...options.attrs },
115
- }),
116
- header: createUiDomPlan('div', 'calendar-header', {
117
- attrs: { id: `${id}-header` },
118
- }),
119
- prevButton: createUiDomPlan('button', 'calendar-nav', {
120
- ...(options.disabled === undefined ? {} : { disabled: options.disabled }),
121
- attrs: {
122
- id: `${id}-prev`,
123
- type: 'button',
124
- 'aria-label': 'Previous month',
125
- 'data-direction': 'prev',
126
- },
127
- }),
128
- nextButton: createUiDomPlan('button', 'calendar-nav', {
129
- ...(options.disabled === undefined ? {} : { disabled: options.disabled }),
130
- attrs: {
131
- id: `${id}-next`,
132
- type: 'button',
133
- 'aria-label': 'Next month',
134
- 'data-direction': 'next',
135
- },
136
- }),
137
- title: createUiDomPlan('span', 'calendar-title', {
138
- attrs: {
139
- id: `${id}-title`,
140
- 'data-label': resolveMonthTitle(display.year, display.month, locale),
141
- },
142
- }),
143
- weekdayRow: createUiDomPlan('div', 'calendar-weekday-row', {
144
- attrs: { id: `${id}-weekday-row` },
145
- }),
146
- weekdays,
147
- grid: createUiDomPlan('div', 'calendar-grid', {
148
- attrs: { id: `${id}-grid`, role: 'grid' },
149
- }),
150
- days,
151
- };
152
- }
153
- export function createDateRangePickerPlans(options) {
154
- const placeholder = options.placeholder ?? 'YYYY-MM-DD';
155
- return {
156
- root: createUiDomPlan('div', 'date-range-picker', {
157
- ...(options.className === undefined ? {} : { className: options.className }),
158
- attrs: { id: `${options.id}-range`, ...options.attrs },
159
- }),
160
- startInput: createUiDomPlan('input', 'date-range-input', {
161
- ...(options.disabled === undefined ? {} : { disabled: options.disabled }),
162
- attrs: {
163
- id: `${options.id}-start`,
164
- type: 'text',
165
- readonly: 'true',
166
- ...(options.startValue === undefined ? {} : { value: options.startValue }),
167
- ...(options.placeholder === undefined ? {} : { placeholder }),
168
- },
169
- }),
170
- separator: createUiDomPlan('span', 'date-range-separator', {
171
- attrs: { id: `${options.id}-sep`, 'data-label': '~' },
172
- }),
173
- endInput: createUiDomPlan('input', 'date-range-input', {
174
- ...(options.disabled === undefined ? {} : { disabled: options.disabled }),
175
- attrs: {
176
- id: `${options.id}-end`,
177
- type: 'text',
178
- readonly: 'true',
179
- ...(options.endValue === undefined ? {} : { value: options.endValue }),
180
- ...(options.placeholder === undefined ? {} : { placeholder }),
181
- },
182
- }),
183
- popover: createUiDomPlan('div', 'date-range-popover', {
184
- state: 'hidden',
185
- attrs: { id: `${options.id}-popover`, role: 'dialog', 'aria-hidden': 'true' },
186
- }),
187
- startCalendar: createUiDomPlan('div', 'date-range-calendar', {
188
- attrs: { id: `${options.id}-start-calendar` },
189
- }),
190
- endCalendar: createUiDomPlan('div', 'date-range-calendar', {
191
- attrs: { id: `${options.id}-end-calendar` },
192
- }),
193
- };
194
- }