@birdycom/omniaui 0.12.5 → 0.12.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (79) hide show
  1. package/README.md +1 -1
  2. package/bin/lib/copy-js.mjs +10 -1
  3. package/bin/omniaui.mjs +2 -2
  4. package/dist/barcode.d.ts +15 -0
  5. package/dist/barcode.js +164 -0
  6. package/dist/components-extra.css +15 -1
  7. package/dist/components.css +1489 -187
  8. package/dist/components.min.css +1 -1
  9. package/dist/data-table.js +135 -98
  10. package/dist/form-extras.d.ts +11 -0
  11. package/dist/form-extras.js +214 -0
  12. package/dist/lazy.d.ts +5 -0
  13. package/dist/lazy.js +37 -0
  14. package/dist/oc-theme.css +1 -1
  15. package/dist/parts/app-data-table-footer.css +38 -0
  16. package/dist/parts/app-data-table-rich.css +161 -0
  17. package/dist/parts/app-data-table.css +44 -74
  18. package/dist/parts/app-name-card.css +65 -0
  19. package/dist/parts/app-titlebar.css +16 -8
  20. package/dist/parts/data-meter-row.css +40 -0
  21. package/dist/parts/data-price.css +20 -0
  22. package/dist/parts/data-ribbon.css +76 -0
  23. package/dist/parts/data-spec-list.css +36 -0
  24. package/dist/parts/data-stock.css +41 -0
  25. package/dist/parts/forms-affix-input.css +1 -1
  26. package/dist/parts/forms-avatar-picker.css +43 -0
  27. package/dist/parts/forms-date-field.css +53 -0
  28. package/dist/parts/forms-density.css +13 -0
  29. package/dist/parts/forms-form-section.css +64 -0
  30. package/dist/parts/forms-quantity.css +55 -0
  31. package/dist/parts/forms-rating.css +81 -0
  32. package/dist/parts/media-attachment.css +1 -1
  33. package/dist/parts/media-barcode.css +23 -0
  34. package/dist/parts/media-chat-composer.css +3 -0
  35. package/dist/parts/media-emoji-picker.css +3 -1
  36. package/dist/parts/media-gallery.css +55 -0
  37. package/dist/parts/media-glass-liquid.css +99 -0
  38. package/dist/parts/media-glass-options.css +50 -0
  39. package/dist/parts/media-lazy.css +46 -0
  40. package/dist/parts/media-message-thread.css +1 -1
  41. package/dist/parts/media-message.css +1 -1
  42. package/dist/parts/nav-topbar-variants.css +96 -0
  43. package/dist/parts/site-header-variants.css +70 -0
  44. package/dist/parts/site-prose.css +1 -1
  45. package/dist/showcase.css +33 -3
  46. package/manifest.json +121 -9
  47. package/package.json +5 -2
  48. package/snippets/accordion-accent.html +1 -1
  49. package/snippets/attachment.html +4 -4
  50. package/snippets/avatar-picker.html +40 -0
  51. package/snippets/barcode.html +20 -0
  52. package/snippets/chat-composer.html +3 -3
  53. package/snippets/data-table.html +51 -76
  54. package/snippets/date-field.html +44 -0
  55. package/snippets/doc-viewer.html +1 -1
  56. package/snippets/emoji-picker.html +1 -1
  57. package/snippets/form-density.html +25 -0
  58. package/snippets/form-section.html +47 -0
  59. package/snippets/gallery.html +25 -0
  60. package/snippets/lazy.html +39 -0
  61. package/snippets/message-thread.html +12 -12
  62. package/snippets/message.html +10 -10
  63. package/snippets/meter-row.html +29 -0
  64. package/snippets/name-card.html +72 -0
  65. package/snippets/page-header.html +1 -1
  66. package/snippets/price.html +25 -0
  67. package/snippets/prose.html +3 -3
  68. package/snippets/quantity.html +12 -0
  69. package/snippets/rating.html +26 -0
  70. package/snippets/ribbon.html +48 -0
  71. package/snippets/sidebar.html +3 -3
  72. package/snippets/spec-list.html +34 -0
  73. package/snippets/stock.html +20 -0
  74. package/snippets/table-actions.html +5 -5
  75. package/snippets/table.html +2 -2
  76. package/snippets/titlebar.html +1 -1
  77. package/snippets/user.html +3 -3
  78. package/dist/parts/app-column-menu.css +0 -85
  79. package/snippets/column-menu.html +0 -38
package/README.md CHANGED
@@ -16,7 +16,7 @@ Plus buttons, cards, badges, alerts, forms, avatar, progress, breadcrumb, pagina
16
16
  ```bash
17
17
  npm install @birdycom/omniaui # import "@birdycom/omniaui"
18
18
  npx omniaui add modal # copy a component into your project (HTML, React, Vue or Blade)
19
- npx omniaui copy-js # the scripts (data table, carousel) to public/js/oc-*.js
19
+ npx omniaui copy-js # the scripts (data table, carousel, barcode, form extras) to public/js/oc-*.js
20
20
  ```
21
21
 
22
22
  MIT
@@ -11,9 +11,16 @@ import { cwd, exists, pkgDir, posix } from "./project.mjs";
11
11
  const SCRIPTS = [
12
12
  ["@birdycom/omniaui", "dist/data-table.js", "oc-data-table.js", "list table: sort, filter, select, paginate"],
13
13
  ["@birdycom/omniaui", "dist/carousel.js", "oc-carousel.js", "looping carousel (.oc-carousel--loop)"],
14
+ ["@birdycom/omniaui", "dist/barcode.js", "oc-barcode.js", "barcode SVG (.oc-barcode: EAN-13, EAN-8, UPC-A, Code 128)"],
15
+ ["@birdycom/omniaui", "dist/lazy.js", "oc-lazy.js", "lazy images: fade in over the placeholder (.oc-lazy)"],
16
+ ["@birdycom/omniaui", "dist/form-extras.js", "oc-form-extras.js", "ISO week in date fields, name card rename, avatar picker preview, quantity steps"],
14
17
  ["@birdycom/omniaui-pro", "dist/spotlight.js", "oc-spotlight.js", "spotlight cards (Pro)"],
15
18
  ["@birdycom/omniaui-pro", "dist/ecosystem-map.js", "oc-ecosystem-map.js", "ecosystem map lines (Pro)"],
16
19
  ["@birdycom/omniaui-pro", "dist/infinite-carousel.js", "oc-infinite-carousel.js", "infinite carousel (Pro)"],
20
+ ["@birdycom/omniaui-pro", "dist/data-table-pro.js", "oc-data-table-pro.js", "data table column width and row drag (Pro)"],
21
+ ["@birdycom/omniaui-pro", "dist/product-picker.js", "oc-product-picker.js", "product picker dialog: search and pick (Pro)"],
22
+ ["@birdycom/omniaui-pro", "dist/shop.js", "oc-shop.js", "shop catalog filters and cart totals (Pro)"],
23
+ ["@birdycom/omniaui-pro", "dist/barcode-maker.js", "oc-barcode-maker.js", "barcode maker: live preview and SVG download (Pro, needs oc-barcode.js)"],
17
24
  ];
18
25
 
19
26
  /** public/js when the project has a public folder (Laravel, Vite, Astro …), else js. */
@@ -40,7 +47,9 @@ export async function cmdCopyJs(names, flags) {
40
47
  if ((await exists(to)) && !flags.force && !flags["dry-run"]) { console.log(`skip ${rel} (exists, use --force)`); continue; }
41
48
  if (!flags["dry-run"]) {
42
49
  await fs.mkdir(dir, { recursive: true });
43
- await fs.copyFile(from, to);
50
+ // A Pro script that builds on a free one imports "@birdycom/omniaui/<name>.js": in the copy that is ./oc-<name>.js.
51
+ const code = await fs.readFile(from, "utf8");
52
+ await fs.writeFile(to, code.replace(/(["'])@birdycom\/omniaui\/([\w-]+)\.js\1/g, "$1./oc-$2.js$1"));
44
53
  }
45
54
  console.log(`${flags["dry-run"] ? "would copy" : "copied"} ${rel} (${what})`);
46
55
  copied.push(target);
package/bin/omniaui.mjs CHANGED
@@ -19,8 +19,8 @@ OmniaUI CLI
19
19
  copy components/blocks into your project (you own the code)
20
20
  omniaui add <name...> -f vue --import print the import line for the npm package instead
21
21
  omniaui list [blocks|components] [-f vue] show what can be added in a flavour
22
- omniaui copy-js [name...] [--to public/js] copy the scripts (data table, carousel; Pro: spotlight, ecosystem map,
23
- infinite carousel) as oc-<name>.js, which start on their own from a script tag
22
+ omniaui copy-js [name...] [--to public/js] copy the scripts (data table, carousel, barcode; Pro: spotlight, ecosystem map,
23
+ infinite carousel, barcode maker …) as oc-<name>.js, which start on their own from a script tag
24
24
 
25
25
  Options:
26
26
  -f, --framework <fw> css | vue | react | blade (default: "framework" in omnia.config.json, else detected
@@ -0,0 +1,15 @@
1
+ // File: packages/ui/src/barcode.d.ts
2
+ // Types for the barcode script (EAN-13, EAN-8, UPC-A, Code 128 as SVG).
3
+
4
+ export type BarcodeFormat = "auto" | "EAN13" | "EAN8" | "UPC" | "CODE128";
5
+ export const CODE128: string[];
6
+ export function guessFormat(value: string): Exclude<BarcodeFormat, "auto">;
7
+ export function eanCheck(body: string): string;
8
+ export function encodeEan(value: string, length: 8 | 13): { bits: string; text: string };
9
+ export function code128Values(text: string): number[];
10
+ export function encodeCode128(text: string): { bits: string; text: string };
11
+ export function encodeBarcode(value: string, format?: BarcodeFormat | string): { bits: string; text: string };
12
+ export function barsToSvg(bits: string, label?: string): string;
13
+ export function barcodeFile(value: string, options?: { format?: BarcodeFormat | string; digits?: boolean; moduleWidth?: number; height?: number }): string;
14
+ export function renderBarcode(el: HTMLElement): void;
15
+ export function initBarcodes(scope?: ParentNode): void;
@@ -0,0 +1,164 @@
1
+ // File: packages/ui/src/barcode.js
2
+ // Map: format guess -> EAN-13 / EAN-8 / UPC-A -> Code 128 (B, or C for even digit runs) -> bars to SVG -> SVG file -> bind -> boot
3
+ // Barcode display without a dependency : <figure class="oc-barcode" data-value="5412345678908">
4
+ // gets an SVG with the bars and the digits under it. data-format="auto|EAN13|EAN8|UPC|CODE128" (default auto).
5
+
6
+ /** Module widths of the Code 128 symbols 0–105 (6 elements, 11 modules each) and the stop (7 elements, 13 modules). */
7
+ export const CODE128 = [
8
+ "212222", "222122", "222221", "121223", "121322", "131222", "122213", "122312", "132212", "221213",
9
+ "221312", "231212", "112232", "122132", "122231", "113222", "123122", "123221", "223211", "221132",
10
+ "221231", "213212", "223112", "312131", "311222", "321122", "321221", "312212", "322112", "322211",
11
+ "212123", "212321", "232121", "111323", "131123", "131321", "112313", "132113", "132311", "211313",
12
+ "231113", "231311", "112133", "112331", "132131", "113123", "113321", "133121", "313121", "211331",
13
+ "231131", "213113", "213311", "213131", "311123", "311321", "331121", "312113", "312311", "332111",
14
+ "314111", "221411", "431111", "111224", "111422", "121124", "121421", "141122", "141221", "112214",
15
+ "112412", "122114", "122411", "142112", "142211", "241211", "221114", "413111", "241112", "134111",
16
+ "111242", "121142", "121241", "114212", "124112", "124211", "411212", "421112", "421211", "212141",
17
+ "214121", "412121", "111143", "111341", "131141", "114113", "114311", "411113", "411311", "113141",
18
+ "114131", "311141", "411131", "211412", "211214", "211232",
19
+ ];
20
+ const STOP = "2331112";
21
+ const START_B = 104;
22
+ const START_C = 105;
23
+
24
+ const L = ["0001101", "0011001", "0010011", "0111101", "0100011", "0110001", "0101111", "0111011", "0110111", "0001011"];
25
+ const G = ["0100111", "0110011", "0011011", "0100001", "0011101", "0111001", "0000101", "0010001", "0001001", "0010111"];
26
+ const R = L.map((code) => code.replace(/./g, (bit) => (bit === "1" ? "0" : "1")));
27
+ const PARITY = ["LLLLLL", "LLGLGG", "LLGGLG", "LLGGGL", "LGLLGG", "LGGLLG", "LGGGLL", "LGLGLG", "LGLGGL", "LGGLGL"];
28
+
29
+ /** EAN-13, EAN-8 or UPC-A for plain digit strings of that length (with or without the check digit); else Code 128. */
30
+ export function guessFormat(value) {
31
+ if (/^\d{12,13}$/.test(value)) return "EAN13";
32
+ if (/^\d{7,8}$/.test(value)) return "EAN8";
33
+ if (/^\d{11}$/.test(value)) return "UPC";
34
+ return "CODE128";
35
+ }
36
+
37
+ /** Check digit of an EAN/UPC body: weights 3 and 1 from the right. */
38
+ export function eanCheck(body) {
39
+ let sum = 0;
40
+ [...body].reverse().forEach((d, i) => { sum += Number(d) * (i % 2 === 0 ? 3 : 1); });
41
+ return String((10 - (sum % 10)) % 10);
42
+ }
43
+
44
+ /** Bars (a "1010…" module string) and the full digits for EAN-13 / EAN-8; throws on a wrong check digit. */
45
+ export function encodeEan(value, length) {
46
+ const body = value.slice(0, length - 1);
47
+ const check = eanCheck(body);
48
+ if (value.length === length && value[length - 1] !== check) throw new Error(`check digit should be ${check}`);
49
+ const digits = body + check;
50
+ if (length === 8) {
51
+ const left = [...digits.slice(0, 4)].map((d) => L[d]).join("");
52
+ const right = [...digits.slice(4)].map((d) => R[d]).join("");
53
+ return { bits: `101${left}01010${right}101`, text: digits };
54
+ }
55
+ const parity = PARITY[digits[0]];
56
+ const left = [...digits.slice(1, 7)].map((d, i) => (parity[i] === "L" ? L : G)[d]).join("");
57
+ const right = [...digits.slice(7)].map((d) => R[d]).join("");
58
+ return { bits: `101${left}01010${right}101`, text: digits };
59
+ }
60
+
61
+ /** Code 128 symbol values for a text: code C for an all-digit text of even length (4+), else code B (ASCII 32–126). */
62
+ export function code128Values(text) {
63
+ let values;
64
+ if (/^(\d\d){2,}$/.test(text)) {
65
+ values = [START_C, ...text.match(/\d\d/g).map(Number)];
66
+ } else {
67
+ if (/[^\x20-\x7e]/.test(text)) throw new Error("Code 128 B takes ASCII characters 32–126 only");
68
+ values = [START_B, ...[...text].map((ch) => ch.charCodeAt(0) - 32)];
69
+ }
70
+ const check = values.reduce((sum, v, i) => sum + v * (i === 0 ? 1 : i), 0) % 103;
71
+ return [...values, check];
72
+ }
73
+
74
+ /** Module string ("1" = bar) for Code 128. */
75
+ export function encodeCode128(text) {
76
+ const widths = code128Values(text).map((v) => CODE128[v]).join("") + STOP;
77
+ let bits = "";
78
+ [...widths].forEach((w, i) => { bits += (i % 2 === 0 ? "1" : "0").repeat(Number(w)); });
79
+ return { bits, text };
80
+ }
81
+
82
+ /** Encode a value in a format (auto picks one). Returns the module string and the human-readable text. */
83
+ export function encodeBarcode(value, format = "auto") {
84
+ const code = String(value ?? "").trim();
85
+ if (!code) throw new Error("empty value");
86
+ const kind = format === "auto" ? guessFormat(code) : format.toUpperCase();
87
+ if (kind === "EAN13") return encodeEan(code.padStart(code.length === 12 ? 12 : 13, "0"), 13);
88
+ if (kind === "UPC") return encodeEan(`0${code}`, 13);
89
+ if (kind === "EAN8") return encodeEan(code, 8);
90
+ return encodeCode128(code);
91
+ }
92
+
93
+ /** Text for HTML: the value can come from user input (Code 128 encodes < > & " too). */
94
+ const escapeHtml = (text) => String(text).replace(/[&<>"']/g, (ch) => `&#${ch.charCodeAt(0)};`);
95
+
96
+ /** SVG markup of the bars, with a quiet zone of 10 modules on both sides. Bars use currentColor. */
97
+ export function barsToSvg(bits, label = "") {
98
+ const quiet = 10;
99
+ let rects = "";
100
+ for (let i = 0; i < bits.length; ) {
101
+ if (bits[i] !== "1") { i++; continue; }
102
+ let w = 1;
103
+ while (bits[i + w] === "1") w++;
104
+ rects += `<rect x="${i + quiet}" y="0" width="${w}" height="50"/>`;
105
+ i += w;
106
+ }
107
+ const width = bits.length + quiet * 2;
108
+ return `<svg viewBox="0 0 ${width} 50" preserveAspectRatio="none" fill="currentColor" role="img" aria-label="${escapeHtml(label)}" style="aspect-ratio:${width}/50">${rects}</svg>`;
109
+ }
110
+
111
+ /** A standalone SVG file (white background, black bars, digits under it) to download, print or put on a label.
112
+ * The barcode maker form (download, live preview) is part of OmniaUI Pro: @birdycom/omniaui-pro/barcode-maker.js.
113
+ * moduleWidth and height are in px; throws like encodeBarcode for a value it cannot encode. */
114
+ export function barcodeFile(value, { format = "auto", digits = true, moduleWidth = 2, height = 70 } = {}) {
115
+ const { bits, text } = encodeBarcode(value, format);
116
+ const quiet = 10;
117
+ const font = 14;
118
+ const width = (bits.length + quiet * 2) * moduleWidth;
119
+ const full = height + (digits ? font + 8 : 0) + 8;
120
+ let rects = "";
121
+ for (let i = 0; i < bits.length; ) {
122
+ if (bits[i] !== "1") { i++; continue; }
123
+ let w = 1;
124
+ while (bits[i + w] === "1") w++;
125
+ rects += `<rect x="${(i + quiet) * moduleWidth}" y="4" width="${w * moduleWidth}" height="${height}"/>`;
126
+ i += w;
127
+ }
128
+ const caption = digits ? `<text x="${width / 2}" y="${height + font + 8}" text-anchor="middle" font-family="ui-monospace, Consolas, monospace" font-size="${font}" letter-spacing="1">${escapeHtml(text)}</text>` : "";
129
+ return `<svg xmlns="http://www.w3.org/2000/svg" width="${width}" height="${full}" viewBox="0 0 ${width} ${full}"><rect width="100%" height="100%" fill="#fff"/><g fill="#000">${rects}${caption}</g></svg>`;
130
+ }
131
+
132
+ /** Draw one .oc-barcode from its data-value and data-format. Shows a short error instead of bars when it cannot. */
133
+ export function renderBarcode(el) {
134
+ if (!el) return;
135
+ const value = el.dataset.value ?? "";
136
+ const digits = el.dataset.digits !== "false";
137
+ try {
138
+ const { bits, text } = encodeBarcode(value, el.dataset.format || "auto");
139
+ el.removeAttribute("data-error");
140
+ el.innerHTML = barsToSvg(bits, `Barcode ${text}`) + (digits ? `<figcaption class="oc-barcode__digits">${escapeHtml(text)}</figcaption>` : "");
141
+ } catch (err) {
142
+ el.setAttribute("data-error", "");
143
+ el.innerHTML = `<figcaption class="oc-barcode__digits">${escapeHtml(value)}: ${escapeHtml(err.message)}</figcaption>`;
144
+ }
145
+ }
146
+
147
+ /** Draw every .oc-barcode[data-value] under scope. */
148
+ export function initBarcodes(scope = document) {
149
+ scope.querySelectorAll(".oc-barcode[data-value]").forEach(renderBarcode);
150
+ }
151
+
152
+ // Starts on its own as /js/oc-barcode.js (`npx omniaui copy-js`), with ?auto in the URL or data-oc-auto on the tag.
153
+ function autoStart() {
154
+ if (typeof document === "undefined") return false;
155
+ const url = String(import.meta.url);
156
+ if (url.includes("oc-barcode") || /[?&]auto(=|&|$)/.test(url)) return true;
157
+ const file = url.split(/[?#]/)[0];
158
+ return [...document.querySelectorAll("script[data-oc-auto][src]")].some((s) => s.src.split(/[?#]/)[0] === file);
159
+ }
160
+ if (autoStart()) {
161
+ const boot = () => initBarcodes(document);
162
+ if (document.readyState === "loading") document.addEventListener("DOMContentLoaded", boot);
163
+ else boot();
164
+ }
@@ -110,7 +110,21 @@
110
110
  .oc-slider:focus-visible::-webkit-slider-thumb { box-shadow: 0 0 0 4px oklch(from var(--oc-ring) l c h / 0.28); }
111
111
  .oc-slider:focus-visible::-moz-range-thumb { box-shadow: 0 0 0 4px oklch(from var(--oc-ring) l c h / 0.28); }
112
112
  .oc-slider:disabled { opacity: 0.5; cursor: not-allowed; }
113
- .oc-slider--vertical { writing-mode: vertical-lr; direction: rtl; inline-size: var(--oc-slider-thumb); block-size: 10rem; }
113
+ /* In a vertical writing mode the inline axis is vertical: inline-size is the height of the slider. */
114
+ .oc-slider--vertical { writing-mode: vertical-lr; direction: rtl; inline-size: 10rem; block-size: var(--oc-slider-thumb); }
115
+ /* Scale under a stepped slider: one mark per step (or per labelled value), centered under the thumb positions.
116
+ The padding keeps the first and last mark under the thumb center at min and max. */
117
+ .oc-slider-scale {
118
+ display: flex;
119
+ justify-content: space-between;
120
+ padding-inline: calc(var(--oc-slider-thumb, 1.125rem) / 2);
121
+ margin-block-start: 0.25rem;
122
+ font-size: 0.6875rem;
123
+ font-variant-numeric: tabular-nums;
124
+ color: var(--oc-muted-foreground);
125
+ }
126
+ .oc-slider-scale > * { display: flex; flex-direction: column; align-items: center; gap: 0.125rem; inline-size: 0; }
127
+ .oc-slider-scale > *::before { content: ""; inline-size: 1px; block-size: 0.375rem; background: var(--oc-border); }
114
128
  .oc-slider--vertical::-webkit-slider-runnable-track {
115
129
  background: linear-gradient(to top, var(--oc-primary) var(--oc-slider-progress), var(--oc-muted) var(--oc-slider-progress));
116
130
  }