@vobs/ui 0.2.0 → 1.0.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 (208) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +60 -0
  3. package/package.json +52 -102
  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 +117 -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 -24
  165. package/dist/app-shell-dom.js +0 -573
  166. package/dist/app-shell.d.ts +0 -199
  167. package/dist/app-shell.js +0 -370
  168. package/dist/base.css +0 -52
  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 -862
  175. package/dist/components.css +0 -4568
  176. package/dist/data-display.d.ts +0 -1063
  177. package/dist/data-display.js +0 -2298
  178. package/dist/feedback.d.ts +0 -88
  179. package/dist/feedback.js +0 -281
  180. package/dist/forms.d.ts +0 -378
  181. package/dist/forms.js +0 -1015
  182. package/dist/icons-config.d.ts +0 -25
  183. package/dist/icons-config.js +0 -14
  184. package/dist/icons.d.ts +0 -41
  185. package/dist/icons.js +0 -185
  186. package/dist/index.d.ts +0 -27
  187. package/dist/index.js +0 -34
  188. package/dist/locale/en-US.d.ts +0 -31
  189. package/dist/locale/en-US.js +0 -20
  190. package/dist/locale/zh-CN.d.ts +0 -31
  191. package/dist/locale/zh-CN.js +0 -20
  192. package/dist/navigation.d.ts +0 -609
  193. package/dist/navigation.js +0 -1707
  194. package/dist/overlay.d.ts +0 -304
  195. package/dist/overlay.js +0 -969
  196. package/dist/primitives.d.ts +0 -268
  197. package/dist/primitives.js +0 -764
  198. package/dist/theme-data.d.ts +0 -363
  199. package/dist/theme-data.js +0 -374
  200. package/dist/theme.d.ts +0 -79
  201. package/dist/theme.js +0 -262
  202. package/dist/tokens.css +0 -449
  203. package/dist/tree.d.ts +0 -144
  204. package/dist/tree.js +0 -469
  205. package/dist/virtual-list.d.ts +0 -190
  206. package/dist/virtual-list.js +0 -521
  207. package/dist/workbench.d.ts +0 -136
  208. package/dist/workbench.js +0 -156
@@ -1,199 +0,0 @@
1
- /** @license MIT
2
- * Copyright (c) 2026 vobsjs
3
- * @vobs/ui
4
- */
5
- import { type UiAttributes, type UiDensity, type UiDomPlan } from './primitives.js';
6
- import { type ReadonlySignal } from '@vobs/reactivity';
7
- import { type RouteLocation } from '@vobs/router';
8
- import { type ComponentProps, type InjectionKey } from '@vobs/runtime-dom';
9
- import { type UiEnvironment } from './icons.js';
10
- import type { BrandInput, ThemeName } from './theme.js';
11
- export type AppShellContentMode = 'contained' | 'scroll';
12
- export type AppShellSidebarMode = 'collapsed' | 'expanded' | 'hidden';
13
- export type AppShellNavItemTone = 'danger' | 'default' | 'success' | 'warning';
14
- export interface AppShellNavItem {
15
- readonly id: string;
16
- readonly label: string;
17
- readonly route?: string;
18
- readonly href?: string;
19
- readonly icon?: string;
20
- readonly match?: string | readonly string[];
21
- readonly active?: boolean;
22
- readonly disabled?: boolean;
23
- readonly badge?: string | number;
24
- readonly tone?: AppShellNavItemTone;
25
- readonly state?: string;
26
- }
27
- export interface AppShellAction {
28
- readonly id: string;
29
- readonly label: string;
30
- readonly icon?: string;
31
- readonly badge?: string | number;
32
- readonly intent?: string;
33
- readonly pressed?: boolean;
34
- readonly disabled?: boolean;
35
- }
36
- export type AppShellAccountMenuItemTone = 'danger' | 'default';
37
- export type AppShellAccountMenuItemKind = 'item' | 'separator';
38
- export interface AppShellAccountMenuItem {
39
- readonly id: string;
40
- readonly label?: string;
41
- readonly kind?: AppShellAccountMenuItemKind;
42
- readonly route?: string;
43
- readonly href?: string;
44
- readonly icon?: string;
45
- readonly disabled?: boolean;
46
- readonly tone?: AppShellAccountMenuItemTone;
47
- }
48
- export interface AppShellBrandConfig {
49
- readonly label: string;
50
- readonly href?: string;
51
- readonly mark?: string;
52
- }
53
- export interface AppShellNavigationConfig {
54
- readonly primary?: readonly AppShellNavItem[];
55
- readonly utility?: readonly AppShellNavItem[];
56
- }
57
- export interface AppShellAccountConfig {
58
- readonly label: string;
59
- readonly initials?: string;
60
- readonly icon?: string;
61
- readonly menu?: readonly AppShellAccountMenuItem[];
62
- readonly menuExpanded?: boolean;
63
- }
64
- export interface AppShellTopbarConfig {
65
- readonly actions?: readonly AppShellAction[];
66
- readonly account?: AppShellAccountConfig;
67
- }
68
- export interface AppShellPageHeaderConfig {
69
- readonly kicker?: string;
70
- readonly titleFallback?: string;
71
- readonly descriptionFallback?: string;
72
- }
73
- export interface AppShellStatusbarConfig {
74
- readonly text?: string;
75
- readonly icon?: string;
76
- readonly version?: string;
77
- readonly pendingText?: string;
78
- readonly pendingIcon?: string;
79
- }
80
- export interface AppShellThemeIconConfig {
81
- readonly dark?: string;
82
- readonly light?: string;
83
- }
84
- export interface AppShellThemeConfig {
85
- readonly mode?: ThemeName;
86
- readonly brand?: BrandInput;
87
- readonly brands?: readonly BrandInput[];
88
- readonly icons?: AppShellThemeIconConfig;
89
- readonly accent?: BrandInput;
90
- readonly persist?: boolean;
91
- }
92
- export interface AppShellRouteConfig {
93
- readonly path?: string;
94
- readonly route?: string;
95
- readonly title?: string;
96
- readonly description?: string;
97
- readonly status?: string;
98
- readonly activeNav?: string;
99
- }
100
- export interface AppShellDefinition {
101
- readonly id: string;
102
- readonly title?: string;
103
- readonly brand: AppShellBrandConfig;
104
- readonly navigation?: AppShellNavigationConfig;
105
- readonly routes?: readonly AppShellRouteConfig[];
106
- readonly pageHeader?: AppShellPageHeaderConfig;
107
- readonly topbar?: AppShellTopbarConfig;
108
- readonly statusbar?: AppShellStatusbarConfig;
109
- readonly sidebarMode?: AppShellSidebarMode;
110
- readonly contentMode?: AppShellContentMode;
111
- readonly theme?: AppShellThemeConfig;
112
- readonly className?: string;
113
- }
114
- export type AppShellDefinitionRecord = Readonly<Record<string, Omit<AppShellDefinition, 'id'> & {
115
- readonly id?: string;
116
- }>>;
117
- export type AppShellConfigInput = AppShellDefinition | readonly AppShellDefinition[] | AppShellDefinitionRecord;
118
- export interface AppShellRegistry {
119
- readonly shells: readonly AppShellDefinition[];
120
- get(id: string): AppShellDefinition | undefined;
121
- }
122
- export interface UiAppShellEnvironment {
123
- readonly appShells: AppShellRegistry;
124
- }
125
- export interface UiAppShellProviderContext {
126
- provide<T>(key: InjectionKey<T>, value: T): () => void;
127
- }
128
- export interface AppShellProps {
129
- readonly shell?: string;
130
- }
131
- export interface ResolvedAppShellState {
132
- readonly activeId: string | undefined;
133
- readonly title: string;
134
- readonly description: string;
135
- readonly status: string;
136
- readonly page: AppShellRouteConfig | undefined;
137
- }
138
- export interface ResolvedAppShellStatus {
139
- readonly state: 'idle' | 'pending';
140
- readonly text: string;
141
- readonly icon?: string;
142
- }
143
- export declare const UiAppShellEnvironmentKey: InjectionKey<UiAppShellEnvironment>;
144
- export declare function defineAppShell<const Shell extends AppShellDefinition>(shell: Shell): Shell;
145
- export declare function defineAppShellConfig<const Config extends AppShellConfigInput>(config: Config): Config;
146
- export declare function createAppShellRegistry(config: AppShellConfigInput): AppShellRegistry;
147
- export declare function provideUiAppShells(context: UiAppShellProviderContext, config: AppShellConfigInput): () => void;
148
- export declare function resolveAppShellState(shell: AppShellDefinition, route: RouteLocation | string | null | undefined): ResolvedAppShellState;
149
- export declare function resolveAppShellStatus(shell: AppShellDefinition, route: RouteLocation | string | null | undefined, pendingRoute: RouteLocation | string | null | undefined): ResolvedAppShellStatus;
150
- export declare function resolveActiveAppShellNavItemId(routePath: string | undefined, items: readonly AppShellNavItem[], activeNav: string | undefined): string | undefined;
151
- export declare const AppShell: import("@vobs/runtime-core").ComponentDefinition<AppShellProps, Record<string, never>, AppShellComponentScope, Readonly<Record<string, Readonly<Record<string, unknown>>>>>;
152
- export interface AppShellComponentScope {
153
- readonly props: ComponentProps<AppShellProps>;
154
- readonly appShells: AppShellRegistry;
155
- readonly icons: UiEnvironment;
156
- readonly currentRoute: ReadonlySignal<RouteLocation | null>;
157
- readonly pendingRoute: ReadonlySignal<RouteLocation | null>;
158
- }
159
- export interface AppShellPlanOptions {
160
- readonly id?: string;
161
- readonly brandLabel: string;
162
- readonly brandHref?: string;
163
- readonly navItems: readonly AppShellNavItem[];
164
- readonly utilityItems?: readonly AppShellNavItem[];
165
- readonly actions?: readonly AppShellAction[];
166
- readonly activeId?: string;
167
- readonly sidebarMode?: AppShellSidebarMode;
168
- readonly contentMode?: AppShellContentMode;
169
- readonly title?: string;
170
- readonly subtitle?: string;
171
- readonly commandLabel?: string;
172
- readonly commandShortcut?: string;
173
- readonly density?: UiDensity;
174
- readonly className?: string;
175
- readonly attrs?: UiAttributes;
176
- }
177
- export interface AppShellPlans {
178
- readonly root: UiDomPlan<'div'>;
179
- readonly sidebar: UiDomPlan<'aside'>;
180
- readonly brand: UiDomPlan<'a'>;
181
- readonly nav: UiDomPlan<'nav'>;
182
- readonly navItems: readonly UiDomPlan<'a'>[];
183
- readonly navBadges: readonly UiDomPlan<'span'>[];
184
- readonly utilityNav: UiDomPlan<'nav'> | undefined;
185
- readonly utilityItems: readonly UiDomPlan<'a'>[];
186
- readonly topbar: UiDomPlan<'header'>;
187
- readonly commandTrigger: UiDomPlan<'button'> | undefined;
188
- readonly actions: readonly UiDomPlan<'button'>[];
189
- readonly main: UiDomPlan<'main'>;
190
- readonly pageFrame: UiDomPlan<'section'>;
191
- readonly pageHeader: UiDomPlan<'header'>;
192
- readonly pageTitle: UiDomPlan<'h1'> | undefined;
193
- readonly pageSubtitle: UiDomPlan<'p'> | undefined;
194
- readonly pageToolbar: UiDomPlan<'div'>;
195
- readonly pageBody: UiDomPlan<'section'>;
196
- }
197
- export declare function createAppShellPlans(options: AppShellPlanOptions): AppShellPlans;
198
- export declare function resolveAppShellSidebarMode(viewportWidth: number, preferred?: AppShellSidebarMode): AppShellSidebarMode;
199
- export declare function getNextAppShellNavItemId(items: readonly AppShellNavItem[], activeId: string | undefined, key: string): string | undefined;
package/dist/app-shell.js DELETED
@@ -1,370 +0,0 @@
1
- /** @license MIT
2
- * Copyright (c) 2026 vobsjs
3
- * @vobs/ui
4
- */
5
- import { createUiDomPlan, } from './primitives.js';
6
- import { effect, signal } from '@vobs/reactivity';
7
- import { CurrentRouteKey, PendingRouteKey } from '@vobs/router';
8
- import { createCompiledTemplate, createInjectionKey, createRuntimeError, defineComponent, mountSlot, } from '@vobs/runtime-dom';
9
- import { bindAppShellAccountMenu, bindAppShellThemeControls, createAppShellFrame, syncAppShellFrame, } from './app-shell-dom.js';
10
- import { createUiEnvironment, UiIconEnvironmentKey } from './icons.js';
11
- export const UiAppShellEnvironmentKey = createInjectionKey('vobs.ui.appShells');
12
- export function defineAppShell(shell) {
13
- return shell;
14
- }
15
- export function defineAppShellConfig(config) {
16
- return config;
17
- }
18
- export function createAppShellRegistry(config) {
19
- const shells = normalizeAppShellConfig(config);
20
- const map = new Map(shells.map((shell) => [shell.id, shell]));
21
- return {
22
- shells,
23
- get(id) {
24
- return map.get(id);
25
- },
26
- };
27
- }
28
- export function provideUiAppShells(context, config) {
29
- return context.provide(UiAppShellEnvironmentKey, {
30
- appShells: createAppShellRegistry(config),
31
- });
32
- }
33
- export function resolveAppShellState(shell, route) {
34
- const routePath = typeof route === 'string' ? route : route?.path;
35
- const page = resolveAppShellPage(shell, routePath);
36
- const navItems = collectAppShellNavItems(shell);
37
- const activeId = resolveActiveAppShellNavItemId(routePath, navItems, page?.activeNav);
38
- return {
39
- activeId,
40
- title: page?.title ?? shell.pageHeader?.titleFallback ?? shell.title ?? '',
41
- description: page?.description ?? shell.pageHeader?.descriptionFallback ?? '',
42
- status: page?.status ?? shell.statusbar?.text ?? '',
43
- page,
44
- };
45
- }
46
- export function resolveAppShellStatus(shell, route, pendingRoute) {
47
- if (pendingRoute !== null && pendingRoute !== undefined) {
48
- const pending = resolveAppShellState(shell, pendingRoute);
49
- const textBase = shell.statusbar?.pendingText ?? 'Loading';
50
- return {
51
- state: 'pending',
52
- text: pending.title.length === 0 ? textBase : `${textBase} ${pending.title}`,
53
- ...(shell.statusbar?.pendingIcon === undefined && shell.statusbar?.icon === undefined
54
- ? {}
55
- : { icon: shell.statusbar?.pendingIcon ?? shell.statusbar?.icon }),
56
- };
57
- }
58
- const current = resolveAppShellState(shell, route);
59
- return {
60
- state: 'idle',
61
- text: current.status || shell.statusbar?.text || '',
62
- ...(shell.statusbar?.icon === undefined ? {} : { icon: shell.statusbar.icon }),
63
- };
64
- }
65
- export function resolveActiveAppShellNavItemId(routePath, items, activeNav) {
66
- if (activeNav !== undefined && items.some((item) => item.id === activeNav))
67
- return activeNav;
68
- if (routePath === undefined)
69
- return items.find((item) => item.active === true)?.id;
70
- const path = normalizeRoutePath(routePath);
71
- const patternMatched = items.find((item) => {
72
- const patterns = normalizeMatchPatterns(item.match);
73
- return patterns.some((pattern) => matchRoutePattern(pattern, path));
74
- });
75
- if (patternMatched !== undefined)
76
- return patternMatched.id;
77
- let best;
78
- for (const item of items) {
79
- const href = routeItemPath(item);
80
- if (href === undefined)
81
- continue;
82
- if (path !== href && !path.startsWith(`${href}/`))
83
- continue;
84
- if (best === undefined || href.length > best.length) {
85
- best = { id: item.id, length: href.length };
86
- }
87
- }
88
- return best?.id;
89
- }
90
- export const AppShell = defineComponent({
91
- template: createCompiledTemplate({
92
- templateId: 'k-app-shell',
93
- sourceId: '@vobs/ui/app-shell',
94
- rootTag: 'div',
95
- mount(owner, scope, environment) {
96
- return mountAppShell(owner, scope, environment);
97
- },
98
- }),
99
- props: ['shell'],
100
- setup(context) {
101
- const currentRoute = context.maybeInject(CurrentRouteKey) ?? signal(null);
102
- const pendingRoute = context.maybeInject(PendingRouteKey) ?? signal(null);
103
- const appShellEnvironment = context.maybeInject(UiAppShellEnvironmentKey);
104
- if (appShellEnvironment === undefined) {
105
- throw createRuntimeError('KUI010', 'App shell config is not provided');
106
- }
107
- return {
108
- props: context.props,
109
- appShells: appShellEnvironment.appShells,
110
- icons: context.maybeInject(UiIconEnvironmentKey) ?? createUiEnvironment(),
111
- currentRoute,
112
- pendingRoute,
113
- };
114
- },
115
- });
116
- export function createAppShellPlans(options) {
117
- const sidebarMode = options.sidebarMode ?? 'expanded';
118
- const activeId = options.activeId ?? options.navItems.find((item) => item.active === true)?.id;
119
- const utilityItems = options.utilityItems ?? [];
120
- return {
121
- root: createUiDomPlan('div', 'app-shell', {
122
- ...(options.className === undefined ? {} : { className: options.className }),
123
- ...(options.density === undefined ? {} : { density: options.density }),
124
- state: sidebarMode,
125
- attrs: {
126
- id: options.id,
127
- ...options.attrs,
128
- 'data-sidebar-mode': sidebarMode,
129
- 'data-content-mode': options.contentMode ?? 'contained',
130
- },
131
- }),
132
- sidebar: createUiDomPlan('aside', 'app-shell-sidebar', {
133
- state: sidebarMode,
134
- attrs: {
135
- id: options.id === undefined ? undefined : `${options.id}-sidebar`,
136
- 'aria-label': 'Primary',
137
- 'data-sidebar-mode': sidebarMode,
138
- },
139
- }),
140
- brand: createUiDomPlan('a', 'app-shell-brand', {
141
- attrs: {
142
- href: options.brandHref ?? '#',
143
- 'aria-label': options.brandLabel,
144
- 'data-label': options.brandLabel,
145
- },
146
- }),
147
- nav: createUiDomPlan('nav', 'app-shell-nav', {
148
- attrs: {
149
- 'aria-label': 'Primary navigation',
150
- 'data-count': options.navItems.length,
151
- },
152
- }),
153
- navItems: options.navItems.map((item) => createNavItemPlan(options, item, activeId)),
154
- navBadges: options.navItems.map((item) => createNavBadgePlan(item)),
155
- utilityNav: utilityItems.length === 0
156
- ? undefined
157
- : createUiDomPlan('nav', 'app-shell-utility-nav', {
158
- attrs: {
159
- 'aria-label': 'Utility navigation',
160
- 'data-count': utilityItems.length,
161
- },
162
- }),
163
- utilityItems: utilityItems.map((item) => createNavItemPlan(options, item, activeId)),
164
- topbar: createUiDomPlan('header', 'app-shell-topbar', {
165
- attrs: {
166
- id: options.id === undefined ? undefined : `${options.id}-topbar`,
167
- 'data-action-count': options.actions?.length ?? 0,
168
- },
169
- }),
170
- commandTrigger: options.commandLabel === undefined
171
- ? undefined
172
- : createUiDomPlan('button', 'app-shell-command-trigger', {
173
- attrs: {
174
- type: 'button',
175
- 'aria-label': options.commandLabel,
176
- 'data-label': options.commandLabel,
177
- 'data-shortcut': options.commandShortcut,
178
- },
179
- }),
180
- actions: (options.actions ?? []).map((action) => createUiDomPlan('button', 'app-shell-action', {
181
- ...(action.disabled === undefined ? {} : { disabled: action.disabled }),
182
- state: action.disabled === true ? 'disabled' : action.pressed === true ? 'pressed' : 'idle',
183
- attrs: {
184
- type: 'button',
185
- 'aria-label': action.label,
186
- 'aria-pressed': action.pressed === undefined ? undefined : action.pressed ? 'true' : 'false',
187
- 'data-action-id': action.id,
188
- 'data-label': action.label,
189
- },
190
- })),
191
- main: createUiDomPlan('main', 'app-shell-main', {
192
- attrs: {
193
- id: options.id === undefined ? undefined : `${options.id}-main`,
194
- },
195
- }),
196
- pageFrame: createUiDomPlan('section', 'app-shell-page-frame'),
197
- pageHeader: createUiDomPlan('header', 'app-shell-page-header'),
198
- pageTitle: options.title === undefined
199
- ? undefined
200
- : createUiDomPlan('h1', 'app-shell-page-title', {
201
- attrs: { 'data-title': options.title },
202
- }),
203
- pageSubtitle: options.subtitle === undefined
204
- ? undefined
205
- : createUiDomPlan('p', 'app-shell-page-subtitle', {
206
- attrs: { 'data-subtitle': options.subtitle },
207
- }),
208
- pageToolbar: createUiDomPlan('div', 'app-shell-page-toolbar', {
209
- attrs: { role: 'toolbar' },
210
- }),
211
- pageBody: createUiDomPlan('section', 'app-shell-page-body', {
212
- state: options.contentMode ?? 'contained',
213
- attrs: {
214
- 'data-mode': options.contentMode ?? 'contained',
215
- },
216
- }),
217
- };
218
- }
219
- export function resolveAppShellSidebarMode(viewportWidth, preferred = 'expanded') {
220
- if (!Number.isFinite(viewportWidth) || viewportWidth < 768)
221
- return 'hidden';
222
- if (viewportWidth < 1100)
223
- return preferred === 'hidden' ? 'hidden' : 'collapsed';
224
- return preferred;
225
- }
226
- export function getNextAppShellNavItemId(items, activeId, key) {
227
- const enabled = items.filter((item) => item.disabled !== true);
228
- if (enabled.length === 0)
229
- return activeId;
230
- const currentIndex = Math.max(-1, enabled.findIndex((item) => item.id === activeId));
231
- if (key === 'Home')
232
- return enabled[0]?.id;
233
- if (key === 'End')
234
- return enabled.at(-1)?.id;
235
- if (key === 'ArrowUp')
236
- return enabled.at(currentIndex - 1)?.id ?? enabled.at(-1)?.id;
237
- if (key === 'ArrowDown')
238
- return enabled[(currentIndex + 1) % enabled.length]?.id;
239
- return activeId;
240
- }
241
- function createNavItemPlan(options, item, activeId) {
242
- const active = item.id === activeId || item.active === true;
243
- return createUiDomPlan('a', 'app-shell-nav-item', {
244
- ...(item.disabled === undefined ? {} : { disabled: item.disabled }),
245
- state: item.disabled === true ? 'disabled' : active ? 'active' : 'idle',
246
- attrs: {
247
- id: options.id === undefined ? undefined : `${options.id}-${item.id}-nav`,
248
- href: item.disabled === true ? undefined : (item.href ?? item.route ?? '#'),
249
- 'aria-current': active ? 'page' : undefined,
250
- tabindex: active && item.disabled !== true ? 0 : -1,
251
- 'data-nav-id': item.id,
252
- 'data-label': item.label,
253
- 'data-tone': item.tone ?? 'default',
254
- 'data-has-badge': item.badge === undefined ? undefined : 'true',
255
- },
256
- });
257
- }
258
- function createNavBadgePlan(item) {
259
- return createUiDomPlan('span', 'app-shell-nav-badge', {
260
- attrs: {
261
- 'aria-hidden': 'true',
262
- 'data-value': item.badge,
263
- },
264
- });
265
- }
266
- function mountAppShell(owner, scope, environment) {
267
- const shell = readAppShell(scope);
268
- const frame = createAppShellFrame(shell, scope, environment);
269
- const pageBody = frame.root.querySelector('.kui-app-shell-page-body');
270
- if (pageBody !== null) {
271
- mountSlot(owner, pageBody, 'default', environment.slots?.default, undefined);
272
- }
273
- bindAppShellThemeControls(owner, frame, shell, scope, environment);
274
- bindAppShellAccountMenu(owner, frame, shell);
275
- syncAppShellFrame(frame, shell, resolveAppShellState(shell, scope.currentRoute.value), resolveAppShellStatus(shell, scope.currentRoute.value, scope.pendingRoute.value), scope, environment);
276
- owner.run(() => {
277
- const stop = effect(() => {
278
- const state = resolveAppShellState(shell, scope.currentRoute.value);
279
- const status = resolveAppShellStatus(shell, scope.currentRoute.value, scope.pendingRoute.value);
280
- syncAppShellFrame(frame, shell, state, status, scope, environment);
281
- }, { scheduler: environment.scheduler });
282
- owner.own(stop);
283
- });
284
- return { root: frame.root, refs: {} };
285
- }
286
- function readAppShell(scope) {
287
- const id = readOptionalString(scope.props.shell?.value);
288
- if (id === undefined)
289
- throw createRuntimeError('KUI011', 'App shell requires `shell`');
290
- const shell = scope.appShells.get(id);
291
- if (shell === undefined)
292
- throw createRuntimeError('KUI012', `App shell is not registered: ${id}`);
293
- return shell;
294
- }
295
- function normalizeAppShellConfig(config) {
296
- if (Array.isArray(config)) {
297
- const list = config;
298
- return list;
299
- }
300
- if ('brand' in config && typeof config.id === 'string') {
301
- const single = config;
302
- return [single];
303
- }
304
- const record = config;
305
- const definitions = [];
306
- for (const id of Object.keys(record)) {
307
- const shell = record[id];
308
- if (shell === undefined)
309
- continue;
310
- definitions.push(shell.id === undefined ? { ...shell, id } : shell);
311
- }
312
- return definitions;
313
- }
314
- function collectAppShellNavItems(shell) {
315
- return [...(shell.navigation?.primary ?? []), ...(shell.navigation?.utility ?? [])];
316
- }
317
- function resolveAppShellPage(shell, routePath) {
318
- if (routePath === undefined)
319
- return undefined;
320
- const path = normalizeRoutePath(routePath);
321
- return [...(shell.routes ?? [])]
322
- .map((page) => ({ page, score: scoreRoutePattern(page.path ?? page.route, path) }))
323
- .filter((candidate) => candidate.score >= 0)
324
- .sort((left, right) => right.score - left.score)[0]?.page;
325
- }
326
- function scoreRoutePattern(pattern, path) {
327
- if (pattern === undefined)
328
- return -1;
329
- if (!matchRoutePattern(pattern, path))
330
- return -1;
331
- const normalized = normalizeRoutePath(pattern);
332
- const staticSegments = normalized.split('/').filter((segment) => !isDynamicSegment(segment));
333
- return normalized.length + staticSegments.length * 10;
334
- }
335
- function matchRoutePattern(pattern, routePath) {
336
- const patternSegments = normalizeRoutePath(pattern).split('/').filter(Boolean);
337
- const routeSegments = normalizeRoutePath(routePath).split('/').filter(Boolean);
338
- for (let index = 0; index < patternSegments.length; index += 1) {
339
- const segment = patternSegments[index];
340
- if (segment === '*')
341
- return true;
342
- if (routeSegments[index] === undefined)
343
- return false;
344
- if (segment !== undefined && !isDynamicSegment(segment) && segment !== routeSegments[index]) {
345
- return false;
346
- }
347
- }
348
- return patternSegments.length === routeSegments.length;
349
- }
350
- function isDynamicSegment(segment) {
351
- return segment !== undefined && (segment.startsWith(':') || /^\[[^\]]+\]$/u.test(segment));
352
- }
353
- function normalizeMatchPatterns(match) {
354
- if (match === undefined)
355
- return [];
356
- return typeof match === 'string' ? [match] : match;
357
- }
358
- function routeItemPath(item) {
359
- const path = item.route ?? item.href;
360
- return path === undefined ? undefined : normalizeRoutePath(path);
361
- }
362
- function normalizeRoutePath(path) {
363
- const [withoutHash] = path.split('#', 1);
364
- const [withoutQuery] = (withoutHash ?? path).split('?', 1);
365
- const normalized = withoutQuery === '' ? '/' : (withoutQuery ?? '/');
366
- return normalized.length > 1 ? normalized.replace(/\/+$/u, '') : normalized;
367
- }
368
- function readOptionalString(value) {
369
- return typeof value === 'string' && value.length > 0 ? value : undefined;
370
- }
package/dist/base.css DELETED
@@ -1,52 +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: 2px solid var(--border-brand);
35
- outline-offset: 2px;
36
- }
37
-
38
- @media (prefers-reduced-motion: reduce) {
39
- :where(*, *::before, *::after) {
40
- scroll-behavior: auto !important;
41
- transition-duration: 0.01ms !important;
42
- animation-duration: 0.01ms !important;
43
- animation-iteration-count: 1 !important;
44
- }
45
- }
46
-
47
- @media (forced-colors: active) {
48
- :where(:focus-visible) {
49
- outline-color: Highlight;
50
- }
51
- }
52
- }
@@ -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>;