@birdycom/omniaui 0.11.0 → 0.11.1

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 (214) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +21 -21
  3. package/bin/lib/add.mjs +126 -126
  4. package/bin/lib/catalog.mjs +169 -169
  5. package/bin/lib/project.mjs +90 -90
  6. package/bin/omniaui.mjs +69 -69
  7. package/dist/code-block.css +25 -25
  8. package/dist/components-extra.css +916 -914
  9. package/dist/components.css +6559 -6469
  10. package/dist/components.min.css +1 -1
  11. package/dist/data-table.d.ts +9 -9
  12. package/dist/data-table.js +505 -505
  13. package/dist/docs-ui.css +267 -265
  14. package/dist/oc-theme.css +69 -69
  15. package/dist/parts/app-accordion-accent.css +60 -60
  16. package/dist/parts/app-alpha-tabs.css +68 -68
  17. package/dist/parts/app-busy-overlay.css +25 -25
  18. package/dist/parts/app-column-menu.css +85 -85
  19. package/dist/parts/app-confirm-dialog.css +70 -70
  20. package/dist/parts/app-cookie-consent.css +63 -63
  21. package/dist/parts/app-data-table.css +174 -174
  22. package/dist/parts/app-greeting.css +47 -47
  23. package/dist/parts/app-list-toolbar.css +81 -81
  24. package/dist/parts/app-page-header.css +54 -54
  25. package/dist/parts/app-severity-card.css +91 -91
  26. package/dist/parts/app-sidebar-dark.css +15 -15
  27. package/dist/parts/app-sortable-row.css +52 -52
  28. package/dist/parts/app-stat-card.css +68 -68
  29. package/dist/parts/app-table-actions.css +71 -71
  30. package/dist/parts/app-table-compact.css +54 -54
  31. package/dist/parts/app-top-progress.css +68 -68
  32. package/dist/parts/app-tree.css +104 -104
  33. package/dist/parts/app-upgrade-banner.css +90 -90
  34. package/dist/parts/app-version-banner.css +80 -80
  35. package/dist/parts/data-chart-line.css +167 -167
  36. package/dist/parts/forms-affix-input.css +75 -75
  37. package/dist/parts/forms-barcode-input.css +75 -75
  38. package/dist/parts/forms-chip-filter.css +91 -91
  39. package/dist/parts/forms-color-select.css +97 -89
  40. package/dist/parts/forms-decimal-input.css +51 -51
  41. package/dist/parts/forms-filter-counts.css +94 -94
  42. package/dist/parts/forms-language-select.css +89 -76
  43. package/dist/parts/forms-number-input.css +98 -95
  44. package/dist/parts/forms-password-requirements.css +55 -55
  45. package/dist/parts/forms-phone-input.css +85 -85
  46. package/dist/parts/forms-time-input.css +74 -74
  47. package/dist/parts/forms-toggle-field.css +69 -69
  48. package/dist/parts/media-art.css +21 -21
  49. package/dist/parts/media-attachment.css +160 -160
  50. package/dist/parts/media-browser-frame.css +73 -73
  51. package/dist/parts/media-card.css +83 -83
  52. package/dist/parts/media-chat-composer.css +90 -90
  53. package/dist/parts/media-doc-viewer.css +151 -151
  54. package/dist/parts/media-emoji-picker.css +88 -88
  55. package/dist/parts/media-entity-card.css +52 -52
  56. package/dist/parts/media-flip-card.css +78 -78
  57. package/dist/parts/media-image-zoom.css +68 -68
  58. package/dist/parts/media-lightbox.css +131 -131
  59. package/dist/parts/media-message-thread.css +136 -136
  60. package/dist/parts/media-message.css +160 -160
  61. package/dist/parts/media-row.css +78 -78
  62. package/dist/parts/media-stack-card.css +82 -82
  63. package/dist/parts/media-terminal-log.css +76 -76
  64. package/dist/parts/media-thumb.css +71 -71
  65. package/dist/parts/site-fact-list.css +25 -25
  66. package/dist/parts/site-link-arrow.css +34 -34
  67. package/dist/parts/site-marquee.css +34 -34
  68. package/dist/parts/site-next-teaser.css +75 -75
  69. package/dist/parts/site-price-card.css +91 -75
  70. package/dist/parts/site-product-mark.css +66 -66
  71. package/dist/parts/site-project-card.css +111 -111
  72. package/dist/parts/site-prose.css +124 -124
  73. package/dist/parts/site-stack-list.css +48 -48
  74. package/dist/parts/site-timeline.css +54 -54
  75. package/dist/showcase.css +404 -404
  76. package/package.json +69 -69
  77. package/snippets/accordion-accent.html +24 -24
  78. package/snippets/accordion.html +11 -11
  79. package/snippets/affix-input.html +30 -30
  80. package/snippets/alert-dialog.html +22 -22
  81. package/snippets/alert.html +5 -5
  82. package/snippets/alpha-tabs.html +37 -37
  83. package/snippets/aspect-ratio.html +15 -15
  84. package/snippets/attachment.html +43 -43
  85. package/snippets/avatar.html +6 -6
  86. package/snippets/back-link.html +5 -5
  87. package/snippets/badge.html +5 -5
  88. package/snippets/barcode-input.html +18 -18
  89. package/snippets/breadcrumb.html +6 -6
  90. package/snippets/browser-frame.html +18 -18
  91. package/snippets/busy-overlay.html +31 -31
  92. package/snippets/button-group.html +21 -21
  93. package/snippets/button.html +8 -8
  94. package/snippets/calendar.html +43 -43
  95. package/snippets/card.html +17 -17
  96. package/snippets/carousel.html +21 -21
  97. package/snippets/chart-line.html +46 -46
  98. package/snippets/chart.html +27 -27
  99. package/snippets/chat-composer.html +21 -21
  100. package/snippets/checkbox.html +5 -5
  101. package/snippets/checklist.html +23 -23
  102. package/snippets/chip-filter.html +14 -14
  103. package/snippets/code-window.html +20 -20
  104. package/snippets/collapsible.html +19 -19
  105. package/snippets/color-field.html +13 -13
  106. package/snippets/color-select.html +18 -18
  107. package/snippets/column-menu.html +38 -38
  108. package/snippets/combobox.html +19 -19
  109. package/snippets/command.html +24 -24
  110. package/snippets/confirm-dialog.html +32 -32
  111. package/snippets/context-menu.html +25 -25
  112. package/snippets/cookie-consent.html +70 -70
  113. package/snippets/copy-field.html +19 -19
  114. package/snippets/counter.html +2 -2
  115. package/snippets/data-table.html +151 -151
  116. package/snippets/date-picker.html +32 -32
  117. package/snippets/decimal-input.html +20 -20
  118. package/snippets/definition-list.html +20 -20
  119. package/snippets/demo-panel.html +18 -18
  120. package/snippets/doc-viewer.html +58 -58
  121. package/snippets/drawer.html +30 -30
  122. package/snippets/dropdown-menu.html +27 -27
  123. package/snippets/dropdown.html +9 -9
  124. package/snippets/dropzone.html +13 -13
  125. package/snippets/emoji-picker.html +51 -51
  126. package/snippets/empty-state.html +17 -17
  127. package/snippets/entity-card.html +26 -26
  128. package/snippets/expandable-table.html +26 -26
  129. package/snippets/fact-list.html +14 -14
  130. package/snippets/filter-counts.html +30 -30
  131. package/snippets/flip-card.html +22 -22
  132. package/snippets/form-field.html +6 -6
  133. package/snippets/greeting.html +22 -22
  134. package/snippets/heading.html +14 -14
  135. package/snippets/hover-card.html +18 -18
  136. package/snippets/image-zoom.html +30 -30
  137. package/snippets/input-group.html +10 -10
  138. package/snippets/input.html +5 -5
  139. package/snippets/item.html +26 -26
  140. package/snippets/kbd.html +13 -13
  141. package/snippets/label.html +9 -9
  142. package/snippets/language-select.html +26 -26
  143. package/snippets/launcher-card.html +14 -14
  144. package/snippets/lightbox.html +50 -50
  145. package/snippets/link-arrow.html +10 -10
  146. package/snippets/list-toolbar.html +33 -33
  147. package/snippets/marquee.html +15 -15
  148. package/snippets/media-card.html +28 -28
  149. package/snippets/media-row.html +21 -21
  150. package/snippets/media-thumb.html +12 -12
  151. package/snippets/menubar.html +30 -30
  152. package/snippets/message-thread.html +123 -123
  153. package/snippets/message.html +51 -51
  154. package/snippets/modal.html +15 -15
  155. package/snippets/native-select.html +15 -15
  156. package/snippets/navmenu.html +23 -23
  157. package/snippets/next-teaser.html +15 -15
  158. package/snippets/number-input.html +27 -27
  159. package/snippets/otp-input.html +9 -9
  160. package/snippets/page-header.html +24 -24
  161. package/snippets/pagination.html +6 -6
  162. package/snippets/password-input.html +24 -24
  163. package/snippets/password-requirements.html +30 -30
  164. package/snippets/phone-input.html +22 -22
  165. package/snippets/popover.html +15 -15
  166. package/snippets/price-card.html +34 -34
  167. package/snippets/product-mark.html +13 -13
  168. package/snippets/progress.html +4 -4
  169. package/snippets/project-card.html +23 -23
  170. package/snippets/prose.html +54 -54
  171. package/snippets/radio.html +17 -17
  172. package/snippets/recovery-codes.html +20 -20
  173. package/snippets/resizable.html +16 -16
  174. package/snippets/scroll-area.html +20 -20
  175. package/snippets/search.html +8 -8
  176. package/snippets/segmented.html +11 -11
  177. package/snippets/select.html +7 -7
  178. package/snippets/separator.html +15 -15
  179. package/snippets/severity-card.html +35 -35
  180. package/snippets/sheet.html +32 -32
  181. package/snippets/sidebar-docs.html +24 -24
  182. package/snippets/sidebar.html +55 -55
  183. package/snippets/site-footer.html +6 -6
  184. package/snippets/site-header.html +26 -26
  185. package/snippets/skeleton.html +4 -4
  186. package/snippets/slider.html +17 -17
  187. package/snippets/sortable-row.html +31 -31
  188. package/snippets/spinner.html +14 -14
  189. package/snippets/stack-card.html +17 -17
  190. package/snippets/stack-list.html +16 -16
  191. package/snippets/stat-card.html +24 -24
  192. package/snippets/stepper.html +13 -13
  193. package/snippets/switch.html +6 -6
  194. package/snippets/table-actions.html +39 -39
  195. package/snippets/table-compact.html +24 -24
  196. package/snippets/table.html +29 -29
  197. package/snippets/tabs.html +9 -9
  198. package/snippets/tag-input.html +60 -17
  199. package/snippets/terminal-log.html +23 -23
  200. package/snippets/textarea.html +3 -3
  201. package/snippets/theme-switch.html +6 -6
  202. package/snippets/time-input.html +26 -26
  203. package/snippets/timeline.html +28 -28
  204. package/snippets/toast-stack.html +10 -10
  205. package/snippets/toast.html +36 -36
  206. package/snippets/toc.html +13 -13
  207. package/snippets/toggle-field.html +20 -20
  208. package/snippets/toggle.html +22 -22
  209. package/snippets/tooltip.html +4 -4
  210. package/snippets/top-progress.html +12 -12
  211. package/snippets/tree.html +38 -38
  212. package/snippets/upgrade-banner.html +28 -28
  213. package/snippets/user.html +15 -15
  214. package/snippets/version-banner.html +15 -15
@@ -1,169 +1,169 @@
1
- // File: packages/ui/bin/lib/catalog.mjs
2
- // What can be added, per flavour (css | vue | react | blade), and which files an entry needs.
3
- // Map: registry (snippet manifests) -> flavourRoot -> catalog -> closure (internal imports) -> findEntries
4
-
5
- import fs from "node:fs/promises";
6
- import path from "node:path";
7
- import { binDir, cwd, exists, kebab, pkgDir, posix, readJson } from "./project.mjs";
8
-
9
- export const INSTALL_HINT = { vue: "npm i @birdycom/omniaui-vue", react: "npm i @birdycom/omniaui-react", blade: "composer require birdycom/omniaui-laravel", block: "npm i @birdycom/omniaui-blocks" };
10
-
11
- // Package roots inside the OmniaUI monorepo (used when the package is not installed, e.g. while developing).
12
- const uiRoot = path.resolve(binDir, "..");
13
- const monoPackages = path.resolve(binDir, "../..");
14
- const monoBlade = path.resolve(binDir, "../../../php-packages/omniaui-laravel/resources/views/components");
15
-
16
- /** Folder of a package, or a monorepo fallback folder, or null. */
17
- async function findPkg(name, fallback) {
18
- try { return pkgDir(name); } catch { /* not installed */ }
19
- return (await exists(path.join(fallback, "package.json"))) ? fallback : null;
20
- }
21
-
22
- const registryCache = {};
23
- /** Snippet registry: { dir, manifest } of @birdycom/omniaui (components) or @birdycom/omniaui-blocks (blocks). */
24
- export async function registry(kind) {
25
- if (!registryCache[kind]) {
26
- const dir = kind === "block" ? await findPkg("@birdycom/omniaui-blocks", path.join(monoPackages, "blocks")) : uiRoot;
27
- if (!dir) throw new Error(`Cannot find the blocks. Install them with: ${INSTALL_HINT.block}`);
28
- registryCache[kind] = { dir, manifest: await readJson(path.join(dir, "manifest.json")) };
29
- }
30
- return registryCache[kind];
31
- }
32
-
33
- /** OmniaUI Pro snippets ({ dir, manifest }) when @birdycom/omniaui-pro is installed, else null. */
34
- async function proRegistry() {
35
- try {
36
- const dir = pkgDir("@birdycom/omniaui-pro");
37
- return { dir, manifest: await readJson(path.join(dir, "manifest.json")) };
38
- } catch { return null; }
39
- }
40
-
41
- /** Names of the OmniaUI Pro blocks (pro.json in @birdycom/omniaui-blocks), for a hint when one is asked for without Pro. */
42
- export async function proBlockNames() {
43
- try { return (await readJson(path.join((await registry("block")).dir, "pro.json"))).items.map((i) => i.name); } catch { return []; }
44
- }
45
-
46
- /** Root folder holding the sources of a flavour (installed package first, monorepo second). Returns null when missing. */
47
- async function flavourRoot(fw) {
48
- if (fw === "vue" || fw === "react") return findPkg(`@birdycom/omniaui-${fw}`, path.join(monoPackages, fw));
49
- if (fw === "blade") {
50
- for (const base of [path.join(cwd, "vendor/birdycom/omniaui-laravel/resources/views/components"), monoBlade]) {
51
- if (await exists(base)) return base;
52
- }
53
- }
54
- return null;
55
- }
56
-
57
- async function walk(dir, base = dir) {
58
- const out = [];
59
- for (const e of await fs.readdir(dir, { withFileTypes: true })) {
60
- const full = path.join(dir, e.name);
61
- if (e.isDirectory()) out.push(...(await walk(full, base)));
62
- else out.push(posix(path.relative(base, full)));
63
- }
64
- return out.sort();
65
- }
66
-
67
- /** Snippet name of a vue manifest item (sub-parts share the snippet of their parent). */
68
- const vueSnippetName = (i) => (i.snippet ? path.basename(i.snippet, ".html") : kebab(i.name.replace(/^Oc(Block)?/, "")));
69
-
70
- /**
71
- * Catalog of one flavour: { root, entries: [{ kind, name, files (relative to root), description }] }.
72
- * An entry groups the files that belong together (Accordion + AccordionItem, accordion.blade.php + accordion/).
73
- */
74
- export async function catalog(fw) {
75
- const describe = {};
76
- const regs = {};
77
- for (const kind of ["component", "block"]) {
78
- // Blocks are optional (separate package): without them, only components are listed.
79
- try { regs[kind] = await registry(kind); } catch (err) { if (kind === "component") throw err; regs[kind] = null; }
80
- for (const i of regs[kind]?.manifest.items || []) describe[`${kind}:${i.name}`] = i;
81
- }
82
- if (fw === "css") {
83
- const entries = [];
84
- for (const kind of ["component", "block"]) {
85
- for (const i of regs[kind]?.manifest.items || []) entries.push({ kind, name: i.name, files: [i.file], description: i.description, root: regs[kind].dir });
86
- }
87
- const pro = await proRegistry();
88
- for (const i of pro?.manifest.items || []) entries.push({ kind: "block", name: i.name, files: [i.file], description: i.description, root: pro.dir, pro: true });
89
- return { root: null, entries };
90
- }
91
- const root = await flavourRoot(fw);
92
- if (!root) throw new Error(`Cannot find the ${fw} sources. Install them with: ${INSTALL_HINT[fw]}`);
93
- const groups = new Map();
94
- const put = (kind, name, file) => {
95
- const key = `${kind}:${name}`;
96
- if (!groups.has(key)) groups.set(key, { kind, name, files: [], description: describe[key]?.description });
97
- groups.get(key).files.push(file);
98
- };
99
- if (fw === "blade") {
100
- for (const file of await walk(root)) {
101
- if (!file.endsWith(".blade.php")) continue;
102
- const segs = file.slice(0, -".blade.php".length).split("/");
103
- if (segs[0] === "block" && segs.length > 1) put("block", segs[1], file);
104
- else put("component", segs[0], file);
105
- }
106
- } else {
107
- const manifest = await readJson(path.join(root, "manifest.json"));
108
- // react: one entry per name; vue: parts that share an HTML snippet belong to one entry
109
- for (const i of manifest.items) put(i.kind, fw === "vue" ? vueSnippetName(i) : i.name, i.file);
110
- }
111
- return { root, entries: [...groups.values()].sort((a, b) => a.name.localeCompare(b.name)) };
112
- }
113
-
114
- const EXT = ["", ".ts", ".tsx", ".vue", "/index.ts", "/index.tsx"];
115
- /** Local files a vue/react source imports (relative specifiers only). */
116
- async function localImports(root, file) {
117
- const text = await fs.readFile(path.join(root, file), "utf8");
118
- const found = [];
119
- for (const m of text.matchAll(/(?:from|import)\s*["'](\.{1,2}\/[^"']+)["']/g)) {
120
- const base = path.posix.normalize(path.posix.join(path.posix.dirname(file), m[1]));
121
- for (const ext of EXT) {
122
- if (path.extname(base + ext) && (await fs.stat(path.join(root, base + ext)).catch(() => null))?.isFile()) { found.push(base + ext); break; }
123
- }
124
- }
125
- return found;
126
- }
127
-
128
- /** Blade components a file uses, as files: <x-oc::button> -> button.blade.php, <x-oc::block.hero> -> block/hero.blade.php. */
129
- async function bladeUses(root, file) {
130
- const text = await fs.readFile(path.join(root, file), "utf8");
131
- const found = new Set();
132
- for (const m of text.matchAll(/<x-oc::([\w.-]+)/g)) {
133
- const rel = `${m[1].replace(/\./g, "/")}.blade.php`;
134
- if (await exists(path.join(root, rel))) found.add(rel);
135
- }
136
- return [...found];
137
- }
138
-
139
- /** All files an entry needs: its own files plus internal dependencies (utils, hooks, composables, sub-components). */
140
- export async function closure(fw, root, entry, entries) {
141
- const seen = new Set();
142
- const queue = [...entry.files];
143
- while (queue.length) {
144
- const file = queue.shift();
145
- if (seen.has(file)) continue;
146
- seen.add(file);
147
- const deps = fw === "blade" ? await bladeUses(root, file) : await localImports(root, file);
148
- for (const dep of deps) {
149
- queue.push(dep);
150
- // blade: pull in the whole entry (root file + sub-parts like accordion/item)
151
- if (fw === "blade") {
152
- const owner = entries.find((e) => e.files.includes(dep));
153
- if (owner) queue.push(...owner.files);
154
- }
155
- }
156
- }
157
- return [...seen].sort();
158
- }
159
-
160
- /** Find the entries for a user-typed name ("button", "OcButton", "app-shell"). */
161
- export function findEntries(entries, input, kind) {
162
- let name = kebab(input);
163
- if (name.startsWith("oc-")) name = name.slice(3);
164
- if (name.startsWith("block-") && !kind) {
165
- const hit = entries.filter((e) => e.kind === "block" && e.name === name.slice(6));
166
- if (hit.length) return hit;
167
- }
168
- return entries.filter((e) => e.name === name && (!kind || e.kind === kind));
169
- }
1
+ // File: packages/ui/bin/lib/catalog.mjs
2
+ // What can be added, per flavour (css | vue | react | blade), and which files an entry needs.
3
+ // Map: registry (snippet manifests) -> flavourRoot -> catalog -> closure (internal imports) -> findEntries
4
+
5
+ import fs from "node:fs/promises";
6
+ import path from "node:path";
7
+ import { binDir, cwd, exists, kebab, pkgDir, posix, readJson } from "./project.mjs";
8
+
9
+ export const INSTALL_HINT = { vue: "npm i @birdycom/omniaui-vue", react: "npm i @birdycom/omniaui-react", blade: "composer require birdycom/omniaui-laravel", block: "npm i @birdycom/omniaui-blocks" };
10
+
11
+ // Package roots inside the OmniaUI monorepo (used when the package is not installed, e.g. while developing).
12
+ const uiRoot = path.resolve(binDir, "..");
13
+ const monoPackages = path.resolve(binDir, "../..");
14
+ const monoBlade = path.resolve(binDir, "../../../php-packages/omniaui-laravel/resources/views/components");
15
+
16
+ /** Folder of a package, or a monorepo fallback folder, or null. */
17
+ async function findPkg(name, fallback) {
18
+ try { return pkgDir(name); } catch { /* not installed */ }
19
+ return (await exists(path.join(fallback, "package.json"))) ? fallback : null;
20
+ }
21
+
22
+ const registryCache = {};
23
+ /** Snippet registry: { dir, manifest } of @birdycom/omniaui (components) or @birdycom/omniaui-blocks (blocks). */
24
+ export async function registry(kind) {
25
+ if (!registryCache[kind]) {
26
+ const dir = kind === "block" ? await findPkg("@birdycom/omniaui-blocks", path.join(monoPackages, "blocks")) : uiRoot;
27
+ if (!dir) throw new Error(`Cannot find the blocks. Install them with: ${INSTALL_HINT.block}`);
28
+ registryCache[kind] = { dir, manifest: await readJson(path.join(dir, "manifest.json")) };
29
+ }
30
+ return registryCache[kind];
31
+ }
32
+
33
+ /** OmniaUI Pro snippets ({ dir, manifest }) when @birdycom/omniaui-pro is installed, else null. */
34
+ async function proRegistry() {
35
+ try {
36
+ const dir = pkgDir("@birdycom/omniaui-pro");
37
+ return { dir, manifest: await readJson(path.join(dir, "manifest.json")) };
38
+ } catch { return null; }
39
+ }
40
+
41
+ /** Names of the OmniaUI Pro blocks (pro.json in @birdycom/omniaui-blocks), for a hint when one is asked for without Pro. */
42
+ export async function proBlockNames() {
43
+ try { return (await readJson(path.join((await registry("block")).dir, "pro.json"))).items.map((i) => i.name); } catch { return []; }
44
+ }
45
+
46
+ /** Root folder holding the sources of a flavour (installed package first, monorepo second). Returns null when missing. */
47
+ async function flavourRoot(fw) {
48
+ if (fw === "vue" || fw === "react") return findPkg(`@birdycom/omniaui-${fw}`, path.join(monoPackages, fw));
49
+ if (fw === "blade") {
50
+ for (const base of [path.join(cwd, "vendor/birdycom/omniaui-laravel/resources/views/components"), monoBlade]) {
51
+ if (await exists(base)) return base;
52
+ }
53
+ }
54
+ return null;
55
+ }
56
+
57
+ async function walk(dir, base = dir) {
58
+ const out = [];
59
+ for (const e of await fs.readdir(dir, { withFileTypes: true })) {
60
+ const full = path.join(dir, e.name);
61
+ if (e.isDirectory()) out.push(...(await walk(full, base)));
62
+ else out.push(posix(path.relative(base, full)));
63
+ }
64
+ return out.sort();
65
+ }
66
+
67
+ /** Snippet name of a vue manifest item (sub-parts share the snippet of their parent). */
68
+ const vueSnippetName = (i) => (i.snippet ? path.basename(i.snippet, ".html") : kebab(i.name.replace(/^Oc(Block)?/, "")));
69
+
70
+ /**
71
+ * Catalog of one flavour: { root, entries: [{ kind, name, files (relative to root), description }] }.
72
+ * An entry groups the files that belong together (Accordion + AccordionItem, accordion.blade.php + accordion/).
73
+ */
74
+ export async function catalog(fw) {
75
+ const describe = {};
76
+ const regs = {};
77
+ for (const kind of ["component", "block"]) {
78
+ // Blocks are optional (separate package): without them, only components are listed.
79
+ try { regs[kind] = await registry(kind); } catch (err) { if (kind === "component") throw err; regs[kind] = null; }
80
+ for (const i of regs[kind]?.manifest.items || []) describe[`${kind}:${i.name}`] = i;
81
+ }
82
+ if (fw === "css") {
83
+ const entries = [];
84
+ for (const kind of ["component", "block"]) {
85
+ for (const i of regs[kind]?.manifest.items || []) entries.push({ kind, name: i.name, files: [i.file], description: i.description, root: regs[kind].dir });
86
+ }
87
+ const pro = await proRegistry();
88
+ for (const i of pro?.manifest.items || []) entries.push({ kind: "block", name: i.name, files: [i.file], description: i.description, root: pro.dir, pro: true });
89
+ return { root: null, entries };
90
+ }
91
+ const root = await flavourRoot(fw);
92
+ if (!root) throw new Error(`Cannot find the ${fw} sources. Install them with: ${INSTALL_HINT[fw]}`);
93
+ const groups = new Map();
94
+ const put = (kind, name, file) => {
95
+ const key = `${kind}:${name}`;
96
+ if (!groups.has(key)) groups.set(key, { kind, name, files: [], description: describe[key]?.description });
97
+ groups.get(key).files.push(file);
98
+ };
99
+ if (fw === "blade") {
100
+ for (const file of await walk(root)) {
101
+ if (!file.endsWith(".blade.php")) continue;
102
+ const segs = file.slice(0, -".blade.php".length).split("/");
103
+ if (segs[0] === "block" && segs.length > 1) put("block", segs[1], file);
104
+ else put("component", segs[0], file);
105
+ }
106
+ } else {
107
+ const manifest = await readJson(path.join(root, "manifest.json"));
108
+ // react: one entry per name; vue: parts that share an HTML snippet belong to one entry
109
+ for (const i of manifest.items) put(i.kind, fw === "vue" ? vueSnippetName(i) : i.name, i.file);
110
+ }
111
+ return { root, entries: [...groups.values()].sort((a, b) => a.name.localeCompare(b.name)) };
112
+ }
113
+
114
+ const EXT = ["", ".ts", ".tsx", ".vue", "/index.ts", "/index.tsx"];
115
+ /** Local files a vue/react source imports (relative specifiers only). */
116
+ async function localImports(root, file) {
117
+ const text = await fs.readFile(path.join(root, file), "utf8");
118
+ const found = [];
119
+ for (const m of text.matchAll(/(?:from|import)\s*["'](\.{1,2}\/[^"']+)["']/g)) {
120
+ const base = path.posix.normalize(path.posix.join(path.posix.dirname(file), m[1]));
121
+ for (const ext of EXT) {
122
+ if (path.extname(base + ext) && (await fs.stat(path.join(root, base + ext)).catch(() => null))?.isFile()) { found.push(base + ext); break; }
123
+ }
124
+ }
125
+ return found;
126
+ }
127
+
128
+ /** Blade components a file uses, as files: <x-oc::button> -> button.blade.php, <x-oc::block.hero> -> block/hero.blade.php. */
129
+ async function bladeUses(root, file) {
130
+ const text = await fs.readFile(path.join(root, file), "utf8");
131
+ const found = new Set();
132
+ for (const m of text.matchAll(/<x-oc::([\w.-]+)/g)) {
133
+ const rel = `${m[1].replace(/\./g, "/")}.blade.php`;
134
+ if (await exists(path.join(root, rel))) found.add(rel);
135
+ }
136
+ return [...found];
137
+ }
138
+
139
+ /** All files an entry needs: its own files plus internal dependencies (utils, hooks, composables, sub-components). */
140
+ export async function closure(fw, root, entry, entries) {
141
+ const seen = new Set();
142
+ const queue = [...entry.files];
143
+ while (queue.length) {
144
+ const file = queue.shift();
145
+ if (seen.has(file)) continue;
146
+ seen.add(file);
147
+ const deps = fw === "blade" ? await bladeUses(root, file) : await localImports(root, file);
148
+ for (const dep of deps) {
149
+ queue.push(dep);
150
+ // blade: pull in the whole entry (root file + sub-parts like accordion/item)
151
+ if (fw === "blade") {
152
+ const owner = entries.find((e) => e.files.includes(dep));
153
+ if (owner) queue.push(...owner.files);
154
+ }
155
+ }
156
+ }
157
+ return [...seen].sort();
158
+ }
159
+
160
+ /** Find the entries for a user-typed name ("button", "OcButton", "app-shell"). */
161
+ export function findEntries(entries, input, kind) {
162
+ let name = kebab(input);
163
+ if (name.startsWith("oc-")) name = name.slice(3);
164
+ if (name.startsWith("block-") && !kind) {
165
+ const hit = entries.filter((e) => e.kind === "block" && e.name === name.slice(6));
166
+ if (hit.length) return hit;
167
+ }
168
+ return entries.filter((e) => e.name === name && (!kind || e.kind === kind));
169
+ }
@@ -1,90 +1,90 @@
1
- // File: packages/ui/bin/lib/project.mjs
2
- // Project helpers for the omniaui CLI: paths, package resolving, config, framework detection, content globs.
3
- // Map: cwd + resolve -> config (omnia.config.json) -> detectFramework / resolveFramework -> defaultDir -> ensureContentGlob
4
-
5
- import fs from "node:fs/promises";
6
- import path from "node:path";
7
- import { createRequire } from "node:module";
8
- import { fileURLToPath } from "node:url";
9
-
10
- export const cwd = process.cwd();
11
- export const CONFIG = "omnia.config.json";
12
- export const FRAMEWORKS = ["css", "vue", "react", "blade"];
13
- // Folder of the omniaui CLI (packages/ui/bin); the package root is one level up.
14
- export const binDir = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
15
- export const requireFromProject = createRequire(path.join(cwd, "package.json"));
16
- const requireFromCli = createRequire(path.join(binDir, "omniaui.mjs"));
17
-
18
- export const exists = async (p) => !!(await fs.stat(p).catch(() => null));
19
- export const posix = (p) => p.split(path.sep).join("/");
20
- export const kebab = (s) => s.replace(/([a-z0-9])([A-Z])/g, "$1-$2").replace(/[_\s]+/g, "-").toLowerCase();
21
- export const pascal = (s) => s.split("-").map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join("");
22
-
23
- export async function readJson(file) {
24
- return JSON.parse(await fs.readFile(file, "utf8"));
25
- }
26
-
27
- /** Folder of an installed package: the project's own install first, then next to the CLI. Throws when missing. */
28
- export function pkgDir(name) {
29
- for (const req of [requireFromProject, requireFromCli]) {
30
- try { return path.dirname(req.resolve(`${name}/package.json`)); } catch { /* try next */ }
31
- }
32
- throw new Error(`Cannot find ${name}. Install it with: npm install ${name}`);
33
- }
34
-
35
- /** omnia.config.json of the project ({} when there is none). */
36
- export async function loadConfig() {
37
- try { return await readJson(path.join(cwd, CONFIG)); } catch { return {}; }
38
- }
39
-
40
- /** Detect the flavour from package.json / composer.json: vue, react, blade (Laravel) or css. */
41
- export async function detectFramework() {
42
- let pkg = {};
43
- try { pkg = await readJson(path.join(cwd, "package.json")); } catch { /* no package.json */ }
44
- const deps = { ...pkg.dependencies, ...pkg.devDependencies };
45
- if (deps.vue || deps.nuxt) return "vue";
46
- if (deps.react || deps.next) return "react";
47
- let laravel = await exists(path.join(cwd, "artisan"));
48
- if (!laravel) {
49
- try { const c = await readJson(path.join(cwd, "composer.json")); laravel = !!{ ...c.require, ...c["require-dev"] }["laravel/framework"]; } catch { /* no composer.json */ }
50
- }
51
- return laravel ? "blade" : "css";
52
- }
53
-
54
- /** Pick the flavour: --framework flag, then config `framework`, then detection. */
55
- export async function resolveFramework(flags) {
56
- const config = await loadConfig();
57
- const fw = String(flags.framework || config.framework || (await detectFramework())).toLowerCase();
58
- if (!FRAMEWORKS.includes(fw)) throw new Error(`Unknown framework "${fw}". Use one of: ${FRAMEWORKS.join(", ")}.`);
59
- return fw;
60
- }
61
-
62
- /** Default target folder per flavour (Laravel projects get Laravel paths). */
63
- export async function defaultDir(fw, legacyKind) {
64
- const laravel = await exists(path.join(cwd, "artisan"));
65
- if (fw === "blade") return "resources/views/components/oc";
66
- if (fw === "css") return legacyKind ? `omnia/${legacyKind}s` : laravel ? "resources/views/oc-snippets" : "oc-snippets";
67
- return laravel || (await exists(path.join(cwd, "resources/js"))) ? "resources/js/components/oc" : "src/components/oc";
68
- }
69
-
70
- /** Does a content glob already cover a sample path? (tiny matcher: **, *, {a,b}) */
71
- function globCovers(glob, sample) {
72
- const re = glob
73
- .replace(/[.+^$()|[\]\\]/g, "\\$&")
74
- .replace(/\{([^}]+)\}/g, (_, g) => `(${g.split(",").join("|")})`)
75
- .replace(/\*\*\//g, "\u0000").replace(/\*\*/g, ".*").replace(/\*/g, "[^/]*").replace(/\u0000/g, "(?:.*/)?");
76
- return new RegExp(`^${re}$`).test(sample);
77
- }
78
-
79
- /** Add a glob to omnia.config.json so "omniacss build" scans copied files (only when a config exists). */
80
- export async function ensureContentGlob(glob, enableBlocks) {
81
- const file = path.join(cwd, CONFIG);
82
- let config;
83
- try { config = await readJson(file); } catch { return; }
84
- let changed = false;
85
- if (enableBlocks && !config.blocks) { config.blocks = true; changed = true; console.log(`Enabled "blocks" in ${CONFIG}`); }
86
- const sample = glob.replace("**/", "x/");
87
- const covered = (config.content || []).some((g) => g === glob || globCovers(g, sample));
88
- if (!covered) { config.content = [...(config.content || []), glob]; changed = true; console.log(`Added "${glob}" to ${CONFIG} content`); }
89
- if (changed) await fs.writeFile(file, JSON.stringify(config, null, 2) + "\n");
90
- }
1
+ // File: packages/ui/bin/lib/project.mjs
2
+ // Project helpers for the omniaui CLI: paths, package resolving, config, framework detection, content globs.
3
+ // Map: cwd + resolve -> config (omnia.config.json) -> detectFramework / resolveFramework -> defaultDir -> ensureContentGlob
4
+
5
+ import fs from "node:fs/promises";
6
+ import path from "node:path";
7
+ import { createRequire } from "node:module";
8
+ import { fileURLToPath } from "node:url";
9
+
10
+ export const cwd = process.cwd();
11
+ export const CONFIG = "omnia.config.json";
12
+ export const FRAMEWORKS = ["css", "vue", "react", "blade"];
13
+ // Folder of the omniaui CLI (packages/ui/bin); the package root is one level up.
14
+ export const binDir = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
15
+ export const requireFromProject = createRequire(path.join(cwd, "package.json"));
16
+ const requireFromCli = createRequire(path.join(binDir, "omniaui.mjs"));
17
+
18
+ export const exists = async (p) => !!(await fs.stat(p).catch(() => null));
19
+ export const posix = (p) => p.split(path.sep).join("/");
20
+ export const kebab = (s) => s.replace(/([a-z0-9])([A-Z])/g, "$1-$2").replace(/[_\s]+/g, "-").toLowerCase();
21
+ export const pascal = (s) => s.split("-").map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join("");
22
+
23
+ export async function readJson(file) {
24
+ return JSON.parse(await fs.readFile(file, "utf8"));
25
+ }
26
+
27
+ /** Folder of an installed package: the project's own install first, then next to the CLI. Throws when missing. */
28
+ export function pkgDir(name) {
29
+ for (const req of [requireFromProject, requireFromCli]) {
30
+ try { return path.dirname(req.resolve(`${name}/package.json`)); } catch { /* try next */ }
31
+ }
32
+ throw new Error(`Cannot find ${name}. Install it with: npm install ${name}`);
33
+ }
34
+
35
+ /** omnia.config.json of the project ({} when there is none). */
36
+ export async function loadConfig() {
37
+ try { return await readJson(path.join(cwd, CONFIG)); } catch { return {}; }
38
+ }
39
+
40
+ /** Detect the flavour from package.json / composer.json: vue, react, blade (Laravel) or css. */
41
+ export async function detectFramework() {
42
+ let pkg = {};
43
+ try { pkg = await readJson(path.join(cwd, "package.json")); } catch { /* no package.json */ }
44
+ const deps = { ...pkg.dependencies, ...pkg.devDependencies };
45
+ if (deps.vue || deps.nuxt) return "vue";
46
+ if (deps.react || deps.next) return "react";
47
+ let laravel = await exists(path.join(cwd, "artisan"));
48
+ if (!laravel) {
49
+ try { const c = await readJson(path.join(cwd, "composer.json")); laravel = !!{ ...c.require, ...c["require-dev"] }["laravel/framework"]; } catch { /* no composer.json */ }
50
+ }
51
+ return laravel ? "blade" : "css";
52
+ }
53
+
54
+ /** Pick the flavour: --framework flag, then config `framework`, then detection. */
55
+ export async function resolveFramework(flags) {
56
+ const config = await loadConfig();
57
+ const fw = String(flags.framework || config.framework || (await detectFramework())).toLowerCase();
58
+ if (!FRAMEWORKS.includes(fw)) throw new Error(`Unknown framework "${fw}". Use one of: ${FRAMEWORKS.join(", ")}.`);
59
+ return fw;
60
+ }
61
+
62
+ /** Default target folder per flavour (Laravel projects get Laravel paths). */
63
+ export async function defaultDir(fw, legacyKind) {
64
+ const laravel = await exists(path.join(cwd, "artisan"));
65
+ if (fw === "blade") return "resources/views/components/oc";
66
+ if (fw === "css") return legacyKind ? `omnia/${legacyKind}s` : laravel ? "resources/views/oc-snippets" : "oc-snippets";
67
+ return laravel || (await exists(path.join(cwd, "resources/js"))) ? "resources/js/components/oc" : "src/components/oc";
68
+ }
69
+
70
+ /** Does a content glob already cover a sample path? (tiny matcher: **, *, {a,b}) */
71
+ function globCovers(glob, sample) {
72
+ const re = glob
73
+ .replace(/[.+^$()|[\]\\]/g, "\\$&")
74
+ .replace(/\{([^}]+)\}/g, (_, g) => `(${g.split(",").join("|")})`)
75
+ .replace(/\*\*\//g, "\u0000").replace(/\*\*/g, ".*").replace(/\*/g, "[^/]*").replace(/\u0000/g, "(?:.*/)?");
76
+ return new RegExp(`^${re}$`).test(sample);
77
+ }
78
+
79
+ /** Add a glob to omnia.config.json so "omniacss build" scans copied files (only when a config exists). */
80
+ export async function ensureContentGlob(glob, enableBlocks) {
81
+ const file = path.join(cwd, CONFIG);
82
+ let config;
83
+ try { config = await readJson(file); } catch { return; }
84
+ let changed = false;
85
+ if (enableBlocks && !config.blocks) { config.blocks = true; changed = true; console.log(`Enabled "blocks" in ${CONFIG}`); }
86
+ const sample = glob.replace("**/", "x/");
87
+ const covered = (config.content || []).some((g) => g === glob || globCovers(g, sample));
88
+ if (!covered) { config.content = [...(config.content || []), glob]; changed = true; console.log(`Added "${glob}" to ${CONFIG} content`); }
89
+ if (changed) await fs.writeFile(file, JSON.stringify(config, null, 2) + "\n");
90
+ }