@miozu/jera 0.0.2 → 0.4.2

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 (82) hide show
  1. package/CLAUDE.md +734 -0
  2. package/README.md +219 -1
  3. package/llms.txt +97 -0
  4. package/package.json +54 -14
  5. package/src/actions/index.js +375 -0
  6. package/src/components/docs/CodeBlock.svelte +203 -0
  7. package/src/components/docs/DocSection.svelte +120 -0
  8. package/src/components/docs/PropsTable.svelte +136 -0
  9. package/src/components/docs/SplitPane.svelte +98 -0
  10. package/src/components/docs/index.js +14 -0
  11. package/src/components/feedback/Alert.svelte +234 -0
  12. package/src/components/feedback/EmptyState.svelte +179 -0
  13. package/src/components/feedback/ProgressBar.svelte +116 -0
  14. package/src/components/feedback/Skeleton.svelte +107 -0
  15. package/src/components/feedback/Spinner.svelte +77 -0
  16. package/src/components/feedback/Toast.svelte +261 -0
  17. package/src/components/forms/Checkbox.svelte +147 -0
  18. package/src/components/forms/Dropzone.svelte +248 -0
  19. package/src/components/forms/FileUpload.svelte +266 -0
  20. package/src/components/forms/IconInput.svelte +184 -0
  21. package/src/components/forms/Input.svelte +121 -0
  22. package/src/components/forms/NumberInput.svelte +225 -0
  23. package/src/components/forms/PinInput.svelte +169 -0
  24. package/src/components/forms/Radio.svelte +143 -0
  25. package/src/components/forms/RadioGroup.svelte +62 -0
  26. package/src/components/forms/RangeSlider.svelte +212 -0
  27. package/src/components/forms/SearchInput.svelte +175 -0
  28. package/src/components/forms/Select.svelte +324 -0
  29. package/src/components/forms/Switch.svelte +159 -0
  30. package/src/components/forms/Textarea.svelte +122 -0
  31. package/src/components/navigation/Accordion.svelte +65 -0
  32. package/src/components/navigation/AccordionItem.svelte +146 -0
  33. package/src/components/navigation/NavigationContainer.svelte +344 -0
  34. package/src/components/navigation/Sidebar.svelte +334 -0
  35. package/src/components/navigation/SidebarAccountGroup.svelte +495 -0
  36. package/src/components/navigation/SidebarAccountItem.svelte +492 -0
  37. package/src/components/navigation/SidebarGroup.svelte +230 -0
  38. package/src/components/navigation/SidebarGroupSwitcher.svelte +262 -0
  39. package/src/components/navigation/SidebarItem.svelte +210 -0
  40. package/src/components/navigation/SidebarNavigationItem.svelte +470 -0
  41. package/src/components/navigation/SidebarPopover.svelte +145 -0
  42. package/src/components/navigation/SidebarSearch.svelte +236 -0
  43. package/src/components/navigation/SidebarSection.svelte +158 -0
  44. package/src/components/navigation/SidebarToggle.svelte +86 -0
  45. package/src/components/navigation/Tabs.svelte +239 -0
  46. package/src/components/navigation/WorkspaceMenu.svelte +416 -0
  47. package/src/components/navigation/blocks/NavigationAccountGroup.svelte +396 -0
  48. package/src/components/navigation/blocks/NavigationCustomBlock.svelte +74 -0
  49. package/src/components/navigation/blocks/NavigationGroupSwitcher.svelte +277 -0
  50. package/src/components/navigation/blocks/NavigationSearch.svelte +300 -0
  51. package/src/components/navigation/blocks/NavigationSection.svelte +230 -0
  52. package/src/components/navigation/index.js +22 -0
  53. package/src/components/overlays/ConfirmDialog.svelte +272 -0
  54. package/src/components/overlays/Dropdown.svelte +153 -0
  55. package/src/components/overlays/DropdownDivider.svelte +23 -0
  56. package/src/components/overlays/DropdownItem.svelte +97 -0
  57. package/src/components/overlays/Modal.svelte +232 -0
  58. package/src/components/overlays/Popover.svelte +206 -0
  59. package/src/components/primitives/Avatar.svelte +132 -0
  60. package/src/components/primitives/Badge.svelte +118 -0
  61. package/src/components/primitives/Button.svelte +214 -0
  62. package/src/components/primitives/Card.svelte +104 -0
  63. package/src/components/primitives/Divider.svelte +105 -0
  64. package/src/components/primitives/LazyImage.svelte +104 -0
  65. package/src/components/primitives/Link.svelte +122 -0
  66. package/src/components/primitives/Stat.svelte +197 -0
  67. package/src/components/primitives/StatusBadge.svelte +122 -0
  68. package/src/index.js +183 -0
  69. package/src/tokens/colors.css +157 -0
  70. package/src/tokens/effects.css +128 -0
  71. package/src/tokens/index.css +81 -0
  72. package/src/tokens/spacing.css +49 -0
  73. package/src/tokens/typography.css +79 -0
  74. package/src/utils/cn.svelte.js +175 -0
  75. package/src/utils/highlighter.js +124 -0
  76. package/src/utils/index.js +22 -0
  77. package/src/utils/navigation.svelte.js +423 -0
  78. package/src/utils/reactive.svelte.js +328 -0
  79. package/src/utils/sidebar.svelte.js +211 -0
  80. package/jera.js +0 -135
  81. package/www/components/jera/Input/Input.svelte +0 -63
  82. package/www/components/jera/Input/index.js +0 -1
@@ -0,0 +1,124 @@
1
+ /**
2
+ * Syntax highlighting utility using Shiki v3 with CSS variables theme.
3
+ * Provides cached highlighter instance for performance.
4
+ * Handles the case when shiki is not installed as an optional dependency.
5
+ *
6
+ * @module @miozu/jera/utils/highlighter
7
+ */
8
+
9
+ // Cache the highlighter to avoid recreating it for each request
10
+ let highlighterPromise = null;
11
+ let shikiAvailable = null;
12
+
13
+ /**
14
+ * Default languages supported by the highlighter
15
+ */
16
+ export const DEFAULT_LANGUAGES = [
17
+ 'javascript',
18
+ 'typescript',
19
+ 'svelte',
20
+ 'jsx',
21
+ 'tsx',
22
+ 'html',
23
+ 'css',
24
+ 'json',
25
+ 'bash',
26
+ 'shell',
27
+ 'markdown',
28
+ 'python',
29
+ 'sql',
30
+ 'yaml',
31
+ 'toml'
32
+ ];
33
+
34
+ /**
35
+ * Check if shiki is available
36
+ * @returns {Promise<boolean>}
37
+ */
38
+ async function isShikiAvailable() {
39
+ if (shikiAvailable !== null) return shikiAvailable;
40
+
41
+ try {
42
+ await import('shiki');
43
+ shikiAvailable = true;
44
+ } catch {
45
+ shikiAvailable = false;
46
+ }
47
+ return shikiAvailable;
48
+ }
49
+
50
+ /**
51
+ * Get or create the Shiki highlighter with CSS variables theme.
52
+ * The highlighter is cached and reused for performance.
53
+ *
54
+ * @returns {Promise<import('shiki').Highlighter|null>}
55
+ */
56
+ export async function getHighlighter() {
57
+ if (!await isShikiAvailable()) {
58
+ return null;
59
+ }
60
+
61
+ if (!highlighterPromise) {
62
+ highlighterPromise = (async () => {
63
+ const shiki = await import('shiki');
64
+
65
+ // Create a CSS variables theme that uses CSS custom properties
66
+ const cssVarsTheme = shiki.createCssVariablesTheme({
67
+ name: 'css-variables',
68
+ variablePrefix: '--shiki-',
69
+ variableDefaults: {},
70
+ fontStyle: true
71
+ });
72
+
73
+ return shiki.createHighlighter({
74
+ themes: [cssVarsTheme],
75
+ langs: DEFAULT_LANGUAGES
76
+ });
77
+ })();
78
+ }
79
+
80
+ return highlighterPromise;
81
+ }
82
+
83
+ /**
84
+ * Highlight code using Shiki with CSS variables theme.
85
+ *
86
+ * @param {string} code - The code to highlight
87
+ * @param {string} [lang='text'] - The language for syntax highlighting
88
+ * @returns {Promise<string>} HTML string with highlighted code
89
+ */
90
+ export async function highlightCode(code, lang = 'text') {
91
+ try {
92
+ const highlighter = await getHighlighter();
93
+
94
+ if (!highlighter) {
95
+ // Shiki not available, return basic escaped code
96
+ return `<pre class="shiki"><code>${escapeHtml(code)}</code></pre>`;
97
+ }
98
+
99
+ const html = highlighter.codeToHtml(code.trim(), {
100
+ lang: lang || 'text',
101
+ theme: 'css-variables'
102
+ });
103
+
104
+ return html;
105
+ } catch (err) {
106
+ console.error('Highlighting error:', err);
107
+ // Fallback to basic escaping
108
+ return `<pre class="shiki"><code>${escapeHtml(code)}</code></pre>`;
109
+ }
110
+ }
111
+
112
+ /**
113
+ * Escape HTML special characters
114
+ * @param {string} str
115
+ * @returns {string}
116
+ */
117
+ function escapeHtml(str) {
118
+ return str
119
+ .replace(/&/g, '&amp;')
120
+ .replace(/</g, '&lt;')
121
+ .replace(/>/g, '&gt;')
122
+ .replace(/"/g, '&quot;')
123
+ .replace(/'/g, '&#039;');
124
+ }
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Jera Utilities
3
+ *
4
+ * Core utilities for class composition and reactive state management.
5
+ */
6
+
7
+ export { cn, rcn, cv, mergeClasses, when, match } from './cn.svelte.js';
8
+ export {
9
+ createReactive,
10
+ createDerived,
11
+ ThemeState,
12
+ getTheme,
13
+ resetTheme,
14
+ createComponentState,
15
+ createIdGenerator,
16
+ generateId
17
+ } from './reactive.svelte.js';
18
+
19
+ export {
20
+ createSidebarState,
21
+ SIDEBAR_CONTEXT_KEY
22
+ } from './sidebar.svelte.js';
@@ -0,0 +1,423 @@
1
+ /**
2
+ * NavigationState - Enterprise-grade reactive state management for navigation
3
+ *
4
+ * Provides centralized state management for complex navigation scenarios including:
5
+ * - Multi-level recursive navigation
6
+ * - Search and filtering across all items
7
+ * - Expansion state management
8
+ * - Active state detection
9
+ * - Breadcrumb generation
10
+ * - Permission-based visibility
11
+ *
12
+ * @example
13
+ * const nav = new NavigationState({
14
+ * items: navigationConfig,
15
+ * persistKey: 'my-app-nav',
16
+ * searchable: true,
17
+ * recursive: true
18
+ * });
19
+ */
20
+
21
+ import { browser } from '$app/environment';
22
+
23
+ export class NavigationState {
24
+ // Core reactive state
25
+ items = $state([]);
26
+ searchQuery = $state('');
27
+ expandedSections = $state({});
28
+ expandedItems = $state({});
29
+ activeItem = $state(null);
30
+ activePath = $state([]);
31
+
32
+ // Configuration
33
+ config = $state({
34
+ persistKey: null,
35
+ searchable: true,
36
+ recursive: true,
37
+ maxDepth: 10,
38
+ autoExpand: false,
39
+ caseSensitiveSearch: false
40
+ });
41
+
42
+ // Computed properties
43
+ filteredItems = $derived.by(() => this.#filterItems(this.items, this.searchQuery));
44
+ breadcrumbs = $derived.by(() => this.#generateBreadcrumbs(this.activeItem));
45
+ totalItemCount = $derived.by(() => this.#countItems(this.items));
46
+ visibleItemCount = $derived.by(() => this.#countItems(this.filteredItems));
47
+
48
+ constructor(options = {}) {
49
+ // Apply configuration
50
+ this.config = { ...this.config, ...options };
51
+
52
+ if (options.items) {
53
+ this.items = this.#normalizeItems(options.items);
54
+ }
55
+
56
+ // Load persisted state
57
+ if (this.config.persistKey && browser) {
58
+ this.#loadPersistedState();
59
+ }
60
+
61
+ // Auto-save state when it changes
62
+ if (this.config.persistKey && browser) {
63
+ $effect(() => {
64
+ this.#savePersistedState();
65
+ });
66
+ }
67
+ }
68
+
69
+ /**
70
+ * Update navigation items with intelligent merging
71
+ */
72
+ setItems(items) {
73
+ this.items = this.#normalizeItems(items);
74
+
75
+ // Auto-expand if configured
76
+ if (this.config.autoExpand) {
77
+ this.#autoExpandItems();
78
+ }
79
+ }
80
+
81
+ /**
82
+ * Set active item by ID or path
83
+ */
84
+ setActive(identifier, shouldExpand = true) {
85
+ const item = typeof identifier === 'string'
86
+ ? this.#findItemById(identifier)
87
+ : identifier;
88
+
89
+ if (item) {
90
+ this.activeItem = item;
91
+ this.activePath = this.#getItemPath(item);
92
+
93
+ if (shouldExpand) {
94
+ this.#expandToItem(item);
95
+ }
96
+ }
97
+ }
98
+
99
+ /**
100
+ * Search navigation items
101
+ */
102
+ search(query) {
103
+ this.searchQuery = query;
104
+
105
+ // Auto-expand search results
106
+ if (query && this.config.autoExpand) {
107
+ this.#expandSearchResults();
108
+ }
109
+ }
110
+
111
+ /**
112
+ * Clear search and collapse expanded results
113
+ */
114
+ clearSearch() {
115
+ this.searchQuery = '';
116
+ }
117
+
118
+ /**
119
+ * Toggle section expansion
120
+ */
121
+ toggleSection(sectionId) {
122
+ this.expandedSections[sectionId] = !this.expandedSections[sectionId];
123
+ }
124
+
125
+ /**
126
+ * Toggle item expansion (for recursive items)
127
+ */
128
+ toggleItem(itemId) {
129
+ this.expandedItems[itemId] = !this.expandedItems[itemId];
130
+ }
131
+
132
+ /**
133
+ * Expand all sections and items
134
+ */
135
+ expandAll() {
136
+ this.#walkItems(this.items, (item) => {
137
+ if (item.children?.length > 0) {
138
+ this.expandedItems[item.id] = true;
139
+ }
140
+ });
141
+
142
+ // Expand all sections
143
+ Object.keys(this.expandedSections).forEach(key => {
144
+ this.expandedSections[key] = true;
145
+ });
146
+ }
147
+
148
+ /**
149
+ * Collapse all sections and items
150
+ */
151
+ collapseAll() {
152
+ this.expandedItems = {};
153
+ Object.keys(this.expandedSections).forEach(key => {
154
+ this.expandedSections[key] = false;
155
+ });
156
+ }
157
+
158
+ /**
159
+ * Get navigation item by ID
160
+ */
161
+ getItem(id) {
162
+ return this.#findItemById(id);
163
+ }
164
+
165
+ /**
166
+ * Get all children of an item
167
+ */
168
+ getChildren(itemId) {
169
+ const item = this.#findItemById(itemId);
170
+ return item?.children || [];
171
+ }
172
+
173
+ /**
174
+ * Get parent of an item
175
+ */
176
+ getParent(itemId) {
177
+ return this.#findParentItem(itemId);
178
+ }
179
+
180
+ /**
181
+ * Get path to root for an item
182
+ */
183
+ getPath(itemId) {
184
+ const item = this.#findItemById(itemId);
185
+ return item ? this.#getItemPath(item) : [];
186
+ }
187
+
188
+ /**
189
+ * Check if item is visible based on permissions
190
+ */
191
+ isVisible(item) {
192
+ if (!item.permissions) return true;
193
+
194
+ // Override in subclass or provide permissions checker
195
+ return this.config.permissionChecker
196
+ ? this.config.permissionChecker(item.permissions)
197
+ : true;
198
+ }
199
+
200
+ /**
201
+ * Check if item is active
202
+ */
203
+ isActive(item) {
204
+ return this.activeItem?.id === item.id;
205
+ }
206
+
207
+ /**
208
+ * Check if item is in active path
209
+ */
210
+ isInActivePath(item) {
211
+ return this.activePath.some(pathItem => pathItem.id === item.id);
212
+ }
213
+
214
+ /**
215
+ * Check if section is expanded
216
+ */
217
+ isSectionExpanded(sectionId) {
218
+ return this.expandedSections[sectionId] ?? false;
219
+ }
220
+
221
+ /**
222
+ * Check if item is expanded
223
+ */
224
+ isItemExpanded(itemId) {
225
+ return this.expandedItems[itemId] ?? false;
226
+ }
227
+
228
+ // Private methods
229
+ #normalizeItems(items) {
230
+ const normalized = [];
231
+
232
+ for (let i = 0; i < items.length; i++) {
233
+ const item = items[i];
234
+ const normalizedItem = {
235
+ id: item.id || `item-${i}`,
236
+ label: item.label || '',
237
+ href: item.href || null,
238
+ icon: item.icon || null,
239
+ badge: item.badge || null,
240
+ children: item.children ? this.#normalizeItems(item.children) : [],
241
+ permissions: item.permissions || null,
242
+ metadata: item.metadata || {},
243
+ depth: item.depth || 0,
244
+ parent: item.parent || null,
245
+ ...item
246
+ };
247
+
248
+ // Set parent references for children
249
+ normalizedItem.children.forEach(child => {
250
+ child.parent = normalizedItem.id;
251
+ child.depth = normalizedItem.depth + 1;
252
+ });
253
+
254
+ normalized.push(normalizedItem);
255
+ }
256
+
257
+ return normalized;
258
+ }
259
+
260
+ #filterItems(items, query) {
261
+ if (!query) return items;
262
+
263
+ const filtered = [];
264
+ const searchLower = this.config.caseSensitiveSearch ? query : query.toLowerCase();
265
+
266
+ for (const item of items) {
267
+ const matches = this.#itemMatches(item, searchLower);
268
+ const filteredChildren = this.#filterItems(item.children || [], query);
269
+
270
+ if (matches || filteredChildren.length > 0) {
271
+ filtered.push({
272
+ ...item,
273
+ children: filteredChildren,
274
+ _matchesSearch: matches
275
+ });
276
+ }
277
+ }
278
+
279
+ return filtered;
280
+ }
281
+
282
+ #itemMatches(item, searchQuery) {
283
+ const searchFields = [
284
+ item.label,
285
+ item.description,
286
+ item.metadata?.tags?.join(' '),
287
+ item.metadata?.keywords?.join(' ')
288
+ ].filter(Boolean);
289
+
290
+ const searchText = this.config.caseSensitiveSearch
291
+ ? searchFields.join(' ')
292
+ : searchFields.join(' ').toLowerCase();
293
+
294
+ return searchText.includes(searchQuery);
295
+ }
296
+
297
+ #findItemById(id, items = this.items) {
298
+ for (const item of items) {
299
+ if (item.id === id) return item;
300
+
301
+ if (item.children) {
302
+ const found = this.#findItemById(id, item.children);
303
+ if (found) return found;
304
+ }
305
+ }
306
+ return null;
307
+ }
308
+
309
+ #findParentItem(itemId, items = this.items, parent = null) {
310
+ for (const item of items) {
311
+ if (item.id === itemId) return parent;
312
+
313
+ if (item.children) {
314
+ const found = this.#findParentItem(itemId, item.children, item);
315
+ if (found) return found;
316
+ }
317
+ }
318
+ return null;
319
+ }
320
+
321
+ #getItemPath(item) {
322
+ const path = [];
323
+ let current = item;
324
+
325
+ while (current) {
326
+ path.unshift(current);
327
+ current = current.parent ? this.#findItemById(current.parent) : null;
328
+ }
329
+
330
+ return path;
331
+ }
332
+
333
+ #expandToItem(item) {
334
+ const path = this.#getItemPath(item);
335
+
336
+ path.forEach(pathItem => {
337
+ if (pathItem.parent) {
338
+ this.expandedItems[pathItem.parent] = true;
339
+ }
340
+ });
341
+ }
342
+
343
+ #expandSearchResults() {
344
+ this.#walkItems(this.filteredItems, (item) => {
345
+ if (item._matchesSearch && item.parent) {
346
+ this.expandedItems[item.parent] = true;
347
+ }
348
+ });
349
+ }
350
+
351
+ #autoExpandItems() {
352
+ this.#walkItems(this.items, (item) => {
353
+ if (item.children?.length > 0) {
354
+ this.expandedItems[item.id] = true;
355
+ }
356
+ });
357
+ }
358
+
359
+ #walkItems(items, callback) {
360
+ for (const item of items) {
361
+ callback(item);
362
+ if (item.children) {
363
+ this.#walkItems(item.children, callback);
364
+ }
365
+ }
366
+ }
367
+
368
+ #countItems(items) {
369
+ let count = 0;
370
+ this.#walkItems(items, () => count++);
371
+ return count;
372
+ }
373
+
374
+ #generateBreadcrumbs(activeItem) {
375
+ if (!activeItem) return [];
376
+ return this.#getItemPath(activeItem);
377
+ }
378
+
379
+ #loadPersistedState() {
380
+ try {
381
+ const saved = localStorage.getItem(this.config.persistKey);
382
+ if (saved) {
383
+ const state = JSON.parse(saved);
384
+ this.expandedSections = state.expandedSections || {};
385
+ this.expandedItems = state.expandedItems || {};
386
+ this.searchQuery = state.searchQuery || '';
387
+ }
388
+ } catch (error) {
389
+ console.warn('Failed to load navigation state:', error);
390
+ }
391
+ }
392
+
393
+ #savePersistedState() {
394
+ try {
395
+ const state = {
396
+ expandedSections: this.expandedSections,
397
+ expandedItems: this.expandedItems,
398
+ searchQuery: this.searchQuery
399
+ };
400
+ localStorage.setItem(this.config.persistKey, JSON.stringify(state));
401
+ } catch (error) {
402
+ console.warn('Failed to save navigation state:', error);
403
+ }
404
+ }
405
+ }
406
+
407
+ /**
408
+ * Create a navigation state instance with common defaults
409
+ */
410
+ export function createNavigationState(options = {}) {
411
+ return new NavigationState({
412
+ searchable: true,
413
+ recursive: true,
414
+ maxDepth: 10,
415
+ autoExpand: false,
416
+ ...options
417
+ });
418
+ }
419
+
420
+ /**
421
+ * Navigation context key for Svelte context
422
+ */
423
+ export const NAVIGATION_CONTEXT_KEY = 'navigation-state';