@birdapi/velinstyle 0.9.0 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. package/README.de.md +15 -11
  2. package/README.md +15 -11
  3. package/cli/cli-manifest.json +1 -1
  4. package/cli/docgen/extract-a11y.js +4 -2
  5. package/cli/index.js +4 -2
  6. package/components/index.js +2 -0
  7. package/components/runtime/component-loaders.js +2 -0
  8. package/components/velin-data-table.js +361 -0
  9. package/components/velin-form-summary.js +348 -0
  10. package/core/a11y/component-contracts.json +18 -1
  11. package/core/highlight/languages/go.js +28 -0
  12. package/core/highlight/languages/python.js +29 -0
  13. package/core/highlight/languages/rust.js +32 -0
  14. package/core/highlight/languages/yaml.js +23 -0
  15. package/core/highlight/registry.js +13 -2
  16. package/core/meta/build.js +1 -1
  17. package/core/search/worker-client.js +2 -3
  18. package/dist/chunks/attributes-353XDOAX.js +391 -0
  19. package/dist/chunks/attributes-ADOKLKA7.js +391 -0
  20. package/dist/chunks/chunk-ERF5YVP4.js +253 -0
  21. package/dist/chunks/chunk-IEHKRARD.js +109 -0
  22. package/dist/chunks/chunk-QVHYL3R4.js +111 -0
  23. package/dist/chunks/go-HZ6XTFCK.js +31 -0
  24. package/dist/chunks/highlight-IBDX4XWS.js +37 -0
  25. package/dist/chunks/python-BMPKDKNO.js +32 -0
  26. package/dist/chunks/runtime-entry.js +1 -1
  27. package/dist/chunks/rust-2BWKI2MN.js +35 -0
  28. package/dist/chunks/velin-code-block-ITCLIC6T.js +132 -0
  29. package/dist/chunks/velin-data-table-KK7IDGTP.js +302 -0
  30. package/dist/chunks/velin-form-summary-XRQ7COQH.js +273 -0
  31. package/dist/chunks/velin-lightbox-NXA3U2FM.js +149 -0
  32. package/dist/chunks/velin-search-FDHEMCSO.js +557 -0
  33. package/dist/chunks/worker-client-MPT7PNQ4.js +47 -0
  34. package/dist/chunks/yaml-O67LEQYT.js +26 -0
  35. package/dist/llms.txt +2 -2
  36. package/dist/search-index.json +28 -2
  37. package/dist/velin-agent.json +31 -5
  38. package/dist/velinstyle-components.iife.js +745 -7
  39. package/dist/velinstyle-components.js +746 -5
  40. package/dist/velinstyle-components.min.js +85 -85
  41. package/dist/velinstyle.css +141 -19
  42. package/dist/velinstyle.d.ts +2 -0
  43. package/dist/velinstyle.min.css +1 -1
  44. package/package.json +8 -2
  45. package/src/base/wc-placeholder.css +7 -0
  46. package/src/components/data-table.css +93 -0
  47. package/src/components/form-validation.css +40 -0
  48. package/src/velinstyle.css +1 -0
  49. package/dist/llms.test.txt +0 -40
  50. package/dist/search-index.test.json +0 -1773
  51. package/dist/velin-agent.test.json +0 -684
@@ -5706,6 +5706,146 @@
5706
5706
  }
5707
5707
  });
5708
5708
 
5709
+ // core/highlight/languages/python.js
5710
+ var python_exports = {};
5711
+ __export(python_exports, {
5712
+ default: () => lexPython
5713
+ });
5714
+ function lexPython(code) {
5715
+ return tokenize(code, RULES9);
5716
+ }
5717
+ var RULES9;
5718
+ var init_python = __esm({
5719
+ "core/highlight/languages/python.js"() {
5720
+ init_utils();
5721
+ RULES9 = [
5722
+ { type: "comment", re: /#[^\n]*/y },
5723
+ // Triple-quoted strings first so the single-quote rules cannot split them.
5724
+ { type: "string", re: /[rbfu]{0,2}"""[\s\S]*?"""/y },
5725
+ { type: "string", re: /[rbfu]{0,2}'''[\s\S]*?'''/y },
5726
+ { type: "string", re: /[rbfu]{0,2}"(?:\\.|[^"\\\n])*"/y },
5727
+ { type: "string", re: /[rbfu]{0,2}'(?:\\.|[^'\\\n])*'/y },
5728
+ { type: "number", re: /\b0[xX][\da-fA-F_]+\b|\b\d[\d_]*(?:\.[\d_]+)?(?:[eE][+-]?\d+)?j?\b/y },
5729
+ {
5730
+ type: "keyword",
5731
+ re: /\b(?:and|as|assert|async|await|break|class|continue|def|del|elif|else|except|finally|for|from|global|if|import|in|is|lambda|match|nonlocal|not|or|pass|raise|return|try|while|with|yield)\b/y
5732
+ },
5733
+ {
5734
+ type: "builtin",
5735
+ re: /\b(?:None|True|False|self|cls|abs|all|any|bool|bytes|dict|dir|enumerate|filter|float|format|frozenset|getattr|hasattr|int|isinstance|issubclass|iter|len|list|map|max|min|next|object|open|print|range|repr|reversed|round|set|setattr|sorted|str|sum|super|tuple|type|zip)\b/y
5736
+ },
5737
+ // Multi-character operators first so `->` is not split into `-` and `>`.
5738
+ { type: "operator", re: /->|:=|\*\*=?|\/\/=?|<<=?|>>=?|[-+*/%&|^~<>!=]=?/y },
5739
+ { type: "punctuation", re: /[[\]{}(),:;.@]/y },
5740
+ { type: "identifier", re: /\b[A-Za-z_]\w*\b/y }
5741
+ ];
5742
+ }
5743
+ });
5744
+
5745
+ // core/highlight/languages/yaml.js
5746
+ var yaml_exports = {};
5747
+ __export(yaml_exports, {
5748
+ default: () => lexYaml
5749
+ });
5750
+ function lexYaml(code) {
5751
+ return tokenize(code, RULES10);
5752
+ }
5753
+ var RULES10;
5754
+ var init_yaml = __esm({
5755
+ "core/highlight/languages/yaml.js"() {
5756
+ init_utils();
5757
+ RULES10 = [
5758
+ { type: "comment", re: /#[^\n]*/y },
5759
+ // Document markers and block scalar indicators.
5760
+ { type: "punctuation", re: /^(?:---|\.\.\.)$/my },
5761
+ { type: "string", re: /"(?:\\.|[^"\\])*"/y },
5762
+ { type: "string", re: /'(?:''|[^'])*'/y },
5763
+ // Keys are the primary structure in YAML, so they get the attr-name colour.
5764
+ { type: "attr-name", re: /^[ \t]*-?[ \t]*[\w.$-]+(?=[ \t]*:(?:[ \t]|$))/my },
5765
+ { type: "punctuation", re: /^[ \t]*-(?=[ \t]|$)/my },
5766
+ { type: "keyword", re: /\b(?:true|false|null|yes|no|on|off|~)\b/yi },
5767
+ { type: "number", re: /\b-?\d+(?:\.\d+)?(?:[eE][+-]?\d+)?\b/y },
5768
+ // Anchors, aliases, tags and block scalar headers.
5769
+ { type: "operator", re: /[&*]\w+|![\w/!-]*|[|>][+-]?\d*(?=\s*$)/my },
5770
+ { type: "punctuation", re: /[:,[\]{}]/y }
5771
+ ];
5772
+ }
5773
+ });
5774
+
5775
+ // core/highlight/languages/go.js
5776
+ var go_exports = {};
5777
+ __export(go_exports, {
5778
+ default: () => lexGo
5779
+ });
5780
+ function lexGo(code) {
5781
+ return tokenize(code, RULES11);
5782
+ }
5783
+ var RULES11;
5784
+ var init_go = __esm({
5785
+ "core/highlight/languages/go.js"() {
5786
+ init_utils();
5787
+ RULES11 = [
5788
+ { type: "comment", re: /\/\/[^\n]*/y },
5789
+ { type: "comment", re: /\/\*[\s\S]*?\*\//y },
5790
+ // Raw strings may span lines.
5791
+ { type: "string", re: /`[^`]*`/y },
5792
+ { type: "string", re: /"(?:\\.|[^"\\\n])*"/y },
5793
+ { type: "string", re: /'(?:\\.|[^'\\\n])*'/y },
5794
+ { type: "number", re: /\b0[xX][\da-fA-F_]+\b|\b\d[\d_]*(?:\.[\d_]*)?(?:[eE][+-]?\d+)?i?\b/y },
5795
+ {
5796
+ type: "keyword",
5797
+ re: /\b(?:break|case|chan|const|continue|default|defer|else|fallthrough|for|func|go|goto|if|import|interface|map|package|range|return|select|struct|switch|type|var)\b/y
5798
+ },
5799
+ {
5800
+ type: "builtin",
5801
+ re: /\b(?:append|bool|byte|cap|clear|close|complex|complex64|complex128|copy|delete|error|float32|float64|imag|int|int8|int16|int32|int64|len|make|max|min|new|nil|panic|print|println|real|recover|rune|string|true|false|iota|uint|uint8|uint16|uint32|uint64|uintptr|any)\b/y
5802
+ },
5803
+ { type: "operator", re: /:=|\.\.\.|&&|\|\||<-|\+\+|--|<<=?|>>=?|&\^=?|[-+*/%&|^<>!=]=?|~/y },
5804
+ { type: "punctuation", re: /[[\]{}(),;:.]/y },
5805
+ { type: "identifier", re: /\b[A-Za-z_]\w*\b/y }
5806
+ ];
5807
+ }
5808
+ });
5809
+
5810
+ // core/highlight/languages/rust.js
5811
+ var rust_exports = {};
5812
+ __export(rust_exports, {
5813
+ default: () => lexRust
5814
+ });
5815
+ function lexRust(code) {
5816
+ return tokenize(code, RULES12);
5817
+ }
5818
+ var RULES12;
5819
+ var init_rust = __esm({
5820
+ "core/highlight/languages/rust.js"() {
5821
+ init_utils();
5822
+ RULES12 = [
5823
+ { type: "comment", re: /\/\/\/?[^\n]*/y },
5824
+ { type: "comment", re: /\/\*[\s\S]*?\*\//y },
5825
+ // Raw strings close on the same number of hashes they opened with.
5826
+ { type: "string", re: /b?r(#*)"[\s\S]*?"\1/y },
5827
+ { type: "string", re: /b?"(?:\\.|[^"\\])*"/y },
5828
+ { type: "string", re: /b?'(?:\\.|[^'\\])'/y },
5829
+ // Attributes such as #[derive(Debug)] read as annotations.
5830
+ { type: "builtin", re: /#!?\[[^\]]*\]/y },
5831
+ { type: "number", re: /\b0[xXbo][\da-fA-F_]+\b|\b\d[\d_]*(?:\.[\d_]+)?(?:[eE][+-]?\d+)?(?:[iuf](?:8|16|32|64|128|size))?\b/y },
5832
+ {
5833
+ type: "keyword",
5834
+ re: /\b(?:as|async|await|break|const|continue|crate|dyn|else|enum|extern|fn|for|if|impl|in|let|loop|match|mod|move|mut|pub|ref|return|self|Self|static|struct|super|trait|type|unsafe|use|where|while)\b/y
5835
+ },
5836
+ {
5837
+ type: "builtin",
5838
+ re: /\b(?:bool|char|f32|f64|i8|i16|i32|i64|i128|isize|str|u8|u16|u32|u64|u128|usize|String|Vec|Option|Result|Some|None|Ok|Err|Box|Rc|Arc|true|false)\b/y
5839
+ },
5840
+ // Lifetimes such as 'a must not be read as an unterminated char literal.
5841
+ { type: "operator", re: /'[a-z_]\w*\b/y },
5842
+ { type: "operator", re: /=>|->|::|\.\.=?|&&|\|\||<<=?|>>=?|[-+*/%&|^<>!=]=?|[?@]/y },
5843
+ { type: "punctuation", re: /[[\]{}(),;:.#]/y },
5844
+ { type: "identifier", re: /\b[A-Za-z_]\w*!?\b/y }
5845
+ ];
5846
+ }
5847
+ });
5848
+
5709
5849
  // core/highlight/registry.js
5710
5850
  function normalizeLanguage(name) {
5711
5851
  const n = (name || "").trim().toLowerCase();
@@ -5725,11 +5865,14 @@
5725
5865
  md: "markdown",
5726
5866
  plaintext: "text",
5727
5867
  "plain-text": "text",
5728
- yml: "text",
5729
- yaml: "text",
5868
+ yml: "yaml",
5730
5869
  toml: "text",
5731
5870
  ini: "text",
5732
- php8: "php"
5871
+ php8: "php",
5872
+ py: "python",
5873
+ python3: "python",
5874
+ golang: "go",
5875
+ rs: "rust"
5733
5876
  };
5734
5877
  return map[n] || n;
5735
5878
  }
@@ -5785,7 +5928,15 @@
5785
5928
  markup: () => Promise.resolve().then(() => (init_html(), html_exports)),
5786
5929
  console: () => Promise.resolve().then(() => (init_plain(), plain_exports)),
5787
5930
  php: () => Promise.resolve().then(() => (init_php(), php_exports)),
5788
- blade: () => Promise.resolve().then(() => (init_blade(), blade_exports))
5931
+ blade: () => Promise.resolve().then(() => (init_blade(), blade_exports)),
5932
+ python: () => Promise.resolve().then(() => (init_python(), python_exports)),
5933
+ py: () => Promise.resolve().then(() => (init_python(), python_exports)),
5934
+ yaml: () => Promise.resolve().then(() => (init_yaml(), yaml_exports)),
5935
+ yml: () => Promise.resolve().then(() => (init_yaml(), yaml_exports)),
5936
+ go: () => Promise.resolve().then(() => (init_go(), go_exports)),
5937
+ golang: () => Promise.resolve().then(() => (init_go(), go_exports)),
5938
+ rust: () => Promise.resolve().then(() => (init_rust(), rust_exports)),
5939
+ rs: () => Promise.resolve().then(() => (init_rust(), rust_exports))
5789
5940
  };
5790
5941
  registerLanguage("js", lexJs);
5791
5942
  }
@@ -6308,6 +6459,589 @@
6308
6459
  }
6309
6460
  });
6310
6461
 
6462
+ // components/velin-data-table.js
6463
+ var velin_data_table_exports = {};
6464
+ __export(velin_data_table_exports, {
6465
+ default: () => velin_data_table_default
6466
+ });
6467
+ function sortValue(cell, type) {
6468
+ const raw = cell?.dataset?.sortValue ?? cell?.textContent ?? "";
6469
+ const text2 = raw.trim();
6470
+ if (type === "number") {
6471
+ const num = Number.parseFloat(text2.replace(/[^\d.,-]/g, "").replace(",", "."));
6472
+ return Number.isNaN(num) ? Number.NEGATIVE_INFINITY : num;
6473
+ }
6474
+ if (type === "date") {
6475
+ const time = Date.parse(text2);
6476
+ return Number.isNaN(time) ? Number.NEGATIVE_INFINITY : time;
6477
+ }
6478
+ return text2.toLowerCase();
6479
+ }
6480
+ var SORT_TYPES, warnedMissingName, VelinDataTable, velin_data_table_default;
6481
+ var init_velin_data_table = __esm({
6482
+ "components/velin-data-table.js"() {
6483
+ init_a11y_utils();
6484
+ SORT_TYPES = /* @__PURE__ */ new Set(["text", "number", "date"]);
6485
+ warnedMissingName = false;
6486
+ VelinDataTable = class extends HTMLElement {
6487
+ static get observedAttributes() {
6488
+ return ["page-size", "filter-input", "empty-text", "label"];
6489
+ }
6490
+ constructor() {
6491
+ super();
6492
+ this._table = null;
6493
+ this._page = 1;
6494
+ this._query = "";
6495
+ this._sortIndex = -1;
6496
+ this._sortDirection = "ascending";
6497
+ this._filterEl = null;
6498
+ this._pagination = null;
6499
+ this._emptyRow = null;
6500
+ this._filterTimer = null;
6501
+ this._onFilterInput = this._onFilterInput.bind(this);
6502
+ }
6503
+ connectedCallback() {
6504
+ this.classList.add("velin-data-table");
6505
+ requestAnimationFrame(() => this._init());
6506
+ }
6507
+ disconnectedCallback() {
6508
+ if (this._filterEl) this._filterEl.removeEventListener("input", this._onFilterInput);
6509
+ if (this._filterTimer) clearTimeout(this._filterTimer);
6510
+ }
6511
+ attributeChangedCallback(name, previous, next) {
6512
+ if (previous === next || !this._table) return;
6513
+ if (name === "page-size") {
6514
+ this._page = 1;
6515
+ this._render();
6516
+ } else if (name === "filter-input") {
6517
+ this._bindFilterInput();
6518
+ } else if (name === "label") {
6519
+ this._ensureAccessibleName();
6520
+ }
6521
+ }
6522
+ // ── Public API ─────────────────────────────────────────────────────────────
6523
+ /** @returns {HTMLTableRowElement[]} */
6524
+ get rows() {
6525
+ const body = this._table?.tBodies?.[0];
6526
+ return body ? [...body.rows].filter((row) => row !== this._emptyRow) : [];
6527
+ }
6528
+ /** Rows matching the current filter, across all pages. */
6529
+ get matchingRows() {
6530
+ return this.rows.filter((row) => !row.dataset.velinFiltered);
6531
+ }
6532
+ /** Rows visible on the current page. */
6533
+ get visibleRows() {
6534
+ return this.matchingRows.filter((row) => !row.hidden);
6535
+ }
6536
+ get page() {
6537
+ return this._page;
6538
+ }
6539
+ get pageSize() {
6540
+ const size = Number.parseInt(this.getAttribute("page-size") || "", 10);
6541
+ return Number.isFinite(size) && size > 0 ? size : 0;
6542
+ }
6543
+ get pageCount() {
6544
+ const size = this.pageSize;
6545
+ if (!size) return 1;
6546
+ return Math.max(1, Math.ceil(this.matchingRows.length / size));
6547
+ }
6548
+ /**
6549
+ * @param {number} index Column index
6550
+ * @param {'ascending' | 'descending'} [direction]
6551
+ */
6552
+ sort(index, direction) {
6553
+ const headers = this._headers();
6554
+ const header = headers[index];
6555
+ if (!header) return;
6556
+ const type = this._sortType(header);
6557
+ if (type === "none") return;
6558
+ this._sortDirection = direction || (this._sortIndex === index && this._sortDirection === "ascending" ? "descending" : "ascending");
6559
+ this._sortIndex = index;
6560
+ const factor = this._sortDirection === "ascending" ? 1 : -1;
6561
+ const body = this._table.tBodies[0];
6562
+ const sorted = this.rows.slice().sort((a, b) => {
6563
+ const av = sortValue(a.cells[index], type);
6564
+ const bv = sortValue(b.cells[index], type);
6565
+ if (av < bv) return -1 * factor;
6566
+ if (av > bv) return 1 * factor;
6567
+ return 0;
6568
+ });
6569
+ for (const row of sorted) body.appendChild(row);
6570
+ this._syncSortState();
6571
+ this._page = 1;
6572
+ this._render();
6573
+ const label = header.dataset.sortLabel || header.textContent.trim();
6574
+ announce(`${label} sorted ${this._sortDirection}`);
6575
+ this.dispatchEvent(new CustomEvent("velin-data-table-sort", {
6576
+ bubbles: true,
6577
+ detail: { index, direction: this._sortDirection, column: label }
6578
+ }));
6579
+ }
6580
+ /** @param {string} query */
6581
+ filter(query) {
6582
+ this._query = String(query || "").trim().toLowerCase();
6583
+ for (const row of this.rows) {
6584
+ const match = !this._query || this._rowText(row).includes(this._query);
6585
+ if (match) delete row.dataset.velinFiltered;
6586
+ else row.dataset.velinFiltered = "true";
6587
+ }
6588
+ this._page = 1;
6589
+ this._render();
6590
+ const count = this.matchingRows.length;
6591
+ announce(count === 1 ? "1 row matches" : `${count} rows match`);
6592
+ this.dispatchEvent(new CustomEvent("velin-data-table-filter", {
6593
+ bubbles: true,
6594
+ detail: { query: this._query, count }
6595
+ }));
6596
+ }
6597
+ /** @param {number} page */
6598
+ goToPage(page) {
6599
+ const target = Math.min(Math.max(1, Math.trunc(page) || 1), this.pageCount);
6600
+ if (target === this._page) return;
6601
+ this._page = target;
6602
+ this._render();
6603
+ announce(`Page ${this._page} of ${this.pageCount}`);
6604
+ this.dispatchEvent(new CustomEvent("velin-data-table-page", {
6605
+ bubbles: true,
6606
+ detail: { page: this._page, pageCount: this.pageCount }
6607
+ }));
6608
+ }
6609
+ // ── Setup ──────────────────────────────────────────────────────────────────
6610
+ _init() {
6611
+ this._table = this.querySelector("table");
6612
+ if (!this._table || !this._table.tBodies.length) return;
6613
+ this._ensureAccessibleName();
6614
+ this._setupSorting();
6615
+ this._bindFilterInput();
6616
+ this._render();
6617
+ }
6618
+ _headers() {
6619
+ const headRow = this._table.tHead?.rows?.[0];
6620
+ return headRow ? [...headRow.cells] : [];
6621
+ }
6622
+ /** @param {HTMLTableCellElement} header */
6623
+ _sortType(header) {
6624
+ const declared = (header.dataset.sort || "").toLowerCase();
6625
+ if (declared === "none") return "none";
6626
+ if (SORT_TYPES.has(declared)) return declared;
6627
+ return this.hasAttribute("sortable") ? "text" : "none";
6628
+ }
6629
+ /**
6630
+ * A table without a name is unusable with a screen reader, so surface it
6631
+ * instead of failing silently (WCAG 1.3.1 / 2.4.6).
6632
+ */
6633
+ _ensureAccessibleName() {
6634
+ const table = this._table;
6635
+ if (!table) return;
6636
+ const label = this.getAttribute("label");
6637
+ const hasName = table.caption?.textContent.trim() || table.getAttribute("aria-label")?.trim() || table.getAttribute("aria-labelledby")?.trim();
6638
+ if (!hasName && label) {
6639
+ table.setAttribute("aria-label", label);
6640
+ return;
6641
+ }
6642
+ if (!hasName && !warnedMissingName) {
6643
+ warnedMissingName = true;
6644
+ console.warn("[velinstyle] <velin-data-table> needs a <caption>, aria-label, or a label attribute.");
6645
+ }
6646
+ }
6647
+ /** Sortable headers get a real button so keyboard and AT support come for free. */
6648
+ _setupSorting() {
6649
+ this._headers().forEach((header, index) => {
6650
+ if (this._sortType(header) === "none") return;
6651
+ if (header.querySelector(".velin-data-table__sort")) return;
6652
+ const label = header.textContent.trim();
6653
+ header.dataset.sortLabel = label;
6654
+ header.setAttribute("aria-sort", "none");
6655
+ header.classList.add("velin-data-table__th");
6656
+ const button = document.createElement("button");
6657
+ button.type = "button";
6658
+ button.className = "velin-data-table__sort";
6659
+ button.textContent = label;
6660
+ const icon = document.createElement("span");
6661
+ icon.className = "velin-data-table__sort-icon";
6662
+ icon.setAttribute("aria-hidden", "true");
6663
+ button.appendChild(icon);
6664
+ button.addEventListener("click", () => this.sort(index));
6665
+ header.textContent = "";
6666
+ header.appendChild(button);
6667
+ });
6668
+ }
6669
+ _syncSortState() {
6670
+ this._headers().forEach((header, index) => {
6671
+ if (this._sortType(header) === "none") return;
6672
+ const active = index === this._sortIndex;
6673
+ header.setAttribute("aria-sort", active ? this._sortDirection : "none");
6674
+ header.querySelector(".velin-data-table__sort")?.classList.toggle("velin-data-table__sort--active", active);
6675
+ });
6676
+ }
6677
+ _bindFilterInput() {
6678
+ if (this._filterEl) this._filterEl.removeEventListener("input", this._onFilterInput);
6679
+ const selector = this.getAttribute("filter-input");
6680
+ this._filterEl = selector ? document.querySelector(selector) : null;
6681
+ if (this._filterEl) this._filterEl.addEventListener("input", this._onFilterInput);
6682
+ }
6683
+ _onFilterInput(event) {
6684
+ if (this._filterTimer) clearTimeout(this._filterTimer);
6685
+ const value = event.target.value;
6686
+ this._filterTimer = setTimeout(() => this.filter(value), 150);
6687
+ }
6688
+ /** @param {HTMLTableRowElement} row */
6689
+ _rowText(row) {
6690
+ const scoped = [...row.cells].filter((cell) => cell.hasAttribute("data-filter"));
6691
+ const cells = scoped.length ? scoped : [...row.cells];
6692
+ return cells.map((cell) => cell.textContent || "").join(" ").toLowerCase();
6693
+ }
6694
+ // ── Rendering ──────────────────────────────────────────────────────────────
6695
+ _render() {
6696
+ const size = this.pageSize;
6697
+ this._page = Math.min(this._page, this.pageCount);
6698
+ const matching = this.matchingRows;
6699
+ const start = size ? (this._page - 1) * size : 0;
6700
+ const end = size ? start + size : matching.length;
6701
+ for (const row of this.rows) {
6702
+ const index = matching.indexOf(row);
6703
+ row.hidden = index === -1 || index < start || index >= end;
6704
+ }
6705
+ this._renderEmptyState(matching.length === 0);
6706
+ this._renderPagination();
6707
+ }
6708
+ /** @param {boolean} isEmpty */
6709
+ _renderEmptyState(isEmpty) {
6710
+ if (!isEmpty) {
6711
+ this._emptyRow?.remove();
6712
+ this._emptyRow = null;
6713
+ return;
6714
+ }
6715
+ if (this._emptyRow?.isConnected) return;
6716
+ const columns = this._headers().length || 1;
6717
+ const row = document.createElement("tr");
6718
+ row.className = "velin-data-table__empty";
6719
+ const cell = document.createElement("td");
6720
+ cell.colSpan = columns;
6721
+ cell.textContent = this.getAttribute("empty-text") || "No matching rows";
6722
+ row.appendChild(cell);
6723
+ this._table.tBodies[0].appendChild(row);
6724
+ this._emptyRow = row;
6725
+ }
6726
+ _renderPagination() {
6727
+ if (!this.pageSize || this.pageCount <= 1) {
6728
+ this._pagination?.remove();
6729
+ this._pagination = null;
6730
+ return;
6731
+ }
6732
+ if (!this._pagination?.isConnected) {
6733
+ const nav = document.createElement("nav");
6734
+ nav.className = "velin-data-table__pagination";
6735
+ nav.setAttribute("aria-label", this.getAttribute("pagination-label") || "Table pagination");
6736
+ const previous2 = document.createElement("button");
6737
+ previous2.type = "button";
6738
+ previous2.className = "velin-btn velin-btn--outline velin-btn--sm";
6739
+ previous2.dataset.velinPage = "previous";
6740
+ previous2.textContent = this.getAttribute("previous-text") || "Previous";
6741
+ previous2.addEventListener("click", () => this.goToPage(this._page - 1));
6742
+ const status2 = document.createElement("p");
6743
+ status2.className = "velin-data-table__page-status";
6744
+ status2.dataset.velinPage = "status";
6745
+ const next2 = document.createElement("button");
6746
+ next2.type = "button";
6747
+ next2.className = "velin-btn velin-btn--outline velin-btn--sm";
6748
+ next2.dataset.velinPage = "next";
6749
+ next2.textContent = this.getAttribute("next-text") || "Next";
6750
+ next2.addEventListener("click", () => this.goToPage(this._page + 1));
6751
+ nav.append(previous2, status2, next2);
6752
+ this.appendChild(nav);
6753
+ this._pagination = nav;
6754
+ }
6755
+ const status = this._pagination.querySelector('[data-velin-page="status"]');
6756
+ if (status) status.textContent = `Page ${this._page} of ${this.pageCount}`;
6757
+ const previous = this._pagination.querySelector('[data-velin-page="previous"]');
6758
+ const next = this._pagination.querySelector('[data-velin-page="next"]');
6759
+ if (previous) previous.disabled = this._page <= 1;
6760
+ if (next) next.disabled = this._page >= this.pageCount;
6761
+ }
6762
+ };
6763
+ customElements.define("velin-data-table", VelinDataTable);
6764
+ velin_data_table_default = VelinDataTable;
6765
+ }
6766
+ });
6767
+
6768
+ // components/velin-form-summary.js
6769
+ var velin_form_summary_exports = {};
6770
+ __export(velin_form_summary_exports, {
6771
+ default: () => velin_form_summary_default
6772
+ });
6773
+ function escapeSelector(value) {
6774
+ const text2 = String(value ?? "");
6775
+ if (typeof CSS !== "undefined" && typeof CSS.escape === "function") return CSS.escape(text2);
6776
+ return text2.replace(/[^\w-]/g, (char) => `\\${char}`);
6777
+ }
6778
+ function fieldLabel(field) {
6779
+ const explicit = field.getAttribute("data-error-label");
6780
+ if (explicit) return explicit;
6781
+ const ariaLabel = field.getAttribute("aria-label");
6782
+ if (ariaLabel?.trim()) return ariaLabel.trim();
6783
+ const labelledBy = field.getAttribute("aria-labelledby");
6784
+ if (labelledBy) {
6785
+ const text2 = labelledBy.split(/\s+/).map((id) => field.ownerDocument.getElementById(id)?.textContent?.trim() || "").filter(Boolean).join(" ");
6786
+ if (text2) return text2;
6787
+ }
6788
+ if (field.id) {
6789
+ const label = field.ownerDocument.querySelector(`label[for="${escapeSelector(field.id)}"]`);
6790
+ if (label?.textContent.trim()) return label.textContent.trim();
6791
+ }
6792
+ const wrapping = field.closest("label");
6793
+ if (wrapping?.textContent.trim()) return wrapping.textContent.trim();
6794
+ return field.name || "This field";
6795
+ }
6796
+ function fieldMessage(field) {
6797
+ return field.getAttribute("data-error-message")?.trim() || field.validationMessage || "Invalid value";
6798
+ }
6799
+ function addDescribedBy(field, id) {
6800
+ const ids = (field.getAttribute("aria-describedby") || "").split(/\s+/).filter(Boolean);
6801
+ if (!ids.includes(id)) {
6802
+ ids.push(id);
6803
+ field.setAttribute("aria-describedby", ids.join(" "));
6804
+ }
6805
+ }
6806
+ function removeDescribedBy(field, id) {
6807
+ const ids = (field.getAttribute("aria-describedby") || "").split(/\s+/).filter(Boolean);
6808
+ const next = ids.filter((value) => value !== id);
6809
+ if (next.length) field.setAttribute("aria-describedby", next.join(" "));
6810
+ else field.removeAttribute("aria-describedby");
6811
+ }
6812
+ var FIELD_SELECTOR, IGNORED_TYPES, fieldIdCounter, VelinFormSummary, velin_form_summary_default;
6813
+ var init_velin_form_summary = __esm({
6814
+ "components/velin-form-summary.js"() {
6815
+ init_a11y_utils();
6816
+ FIELD_SELECTOR = "input, select, textarea";
6817
+ IGNORED_TYPES = /* @__PURE__ */ new Set(["submit", "reset", "button", "image", "hidden"]);
6818
+ fieldIdCounter = 0;
6819
+ VelinFormSummary = class extends HTMLElement {
6820
+ static get observedAttributes() {
6821
+ return ["for", "heading"];
6822
+ }
6823
+ constructor() {
6824
+ super();
6825
+ this._form = null;
6826
+ this._panel = null;
6827
+ this._errors = [];
6828
+ this._onSubmit = this._onSubmit.bind(this);
6829
+ this._onFieldChange = this._onFieldChange.bind(this);
6830
+ this._onReset = this._onReset.bind(this);
6831
+ }
6832
+ connectedCallback() {
6833
+ this.classList.add("velin-form-summary");
6834
+ requestAnimationFrame(() => this._bindForm());
6835
+ }
6836
+ disconnectedCallback() {
6837
+ this._unbindForm();
6838
+ }
6839
+ attributeChangedCallback(name, previous, next) {
6840
+ if (previous === next) return;
6841
+ if (name === "for" && this.isConnected) {
6842
+ this._unbindForm();
6843
+ this._bindForm();
6844
+ } else if (name === "heading" && this._panel) {
6845
+ const heading = this._panel.querySelector(".velin-form-summary__heading");
6846
+ if (heading) heading.textContent = this.headingText;
6847
+ }
6848
+ }
6849
+ // ── Public API ─────────────────────────────────────────────────────────────
6850
+ get form() {
6851
+ return this._form;
6852
+ }
6853
+ /** @returns {{ field: HTMLElement, label: string, message: string }[]} */
6854
+ get errors() {
6855
+ return this._errors.slice();
6856
+ }
6857
+ get headingText() {
6858
+ return this.getAttribute("heading") || "There is a problem";
6859
+ }
6860
+ /** Validate the form and render the summary. @returns {boolean} valid */
6861
+ validate() {
6862
+ if (!this._form) return true;
6863
+ const errors = [];
6864
+ for (const field of this._fields()) {
6865
+ if (field.checkValidity()) {
6866
+ this._clearFieldError(field);
6867
+ continue;
6868
+ }
6869
+ const error = { field, label: fieldLabel(field), message: fieldMessage(field) };
6870
+ this._markFieldError(field, error.message);
6871
+ errors.push(error);
6872
+ }
6873
+ this._errors = errors;
6874
+ this._render();
6875
+ return errors.length === 0;
6876
+ }
6877
+ /** Remove the summary and all field error state. */
6878
+ clear() {
6879
+ for (const field of this._fields()) this._clearFieldError(field);
6880
+ this._errors = [];
6881
+ this._render();
6882
+ }
6883
+ /** Move focus to the first field with an error. */
6884
+ focusFirstError() {
6885
+ const first = this._errors[0];
6886
+ if (first) this._focusField(first.field);
6887
+ }
6888
+ // ── Form wiring ────────────────────────────────────────────────────────────
6889
+ _bindForm() {
6890
+ const id = this.getAttribute("for");
6891
+ this._form = id ? this.ownerDocument.getElementById(id) : this.closest("form");
6892
+ if (!this._form) return;
6893
+ if (!this.hasAttribute("native-validation")) this._form.noValidate = true;
6894
+ this._form.addEventListener("submit", this._onSubmit);
6895
+ this._form.addEventListener("reset", this._onReset);
6896
+ this._form.addEventListener("input", this._onFieldChange);
6897
+ this._form.addEventListener("change", this._onFieldChange);
6898
+ }
6899
+ _unbindForm() {
6900
+ if (!this._form) return;
6901
+ this._form.removeEventListener("submit", this._onSubmit);
6902
+ this._form.removeEventListener("reset", this._onReset);
6903
+ this._form.removeEventListener("input", this._onFieldChange);
6904
+ this._form.removeEventListener("change", this._onFieldChange);
6905
+ this._form = null;
6906
+ }
6907
+ /** @returns {HTMLElement[]} */
6908
+ _fields() {
6909
+ if (!this._form) return [];
6910
+ const seenRadioNames = /* @__PURE__ */ new Set();
6911
+ return [...this._form.querySelectorAll(FIELD_SELECTOR)].filter((field) => {
6912
+ if (IGNORED_TYPES.has(field.type)) return false;
6913
+ if (field.disabled || field.hasAttribute("data-error-ignore")) return false;
6914
+ if (typeof field.checkValidity !== "function") return false;
6915
+ if (field.type === "radio" && field.name) {
6916
+ if (seenRadioNames.has(field.name)) return false;
6917
+ seenRadioNames.add(field.name);
6918
+ }
6919
+ return true;
6920
+ });
6921
+ }
6922
+ _onSubmit(event) {
6923
+ if (this.validate()) return;
6924
+ event.preventDefault();
6925
+ this._announceErrors();
6926
+ this._focusPanel();
6927
+ this.dispatchEvent(new CustomEvent("velin-form-invalid", {
6928
+ bubbles: true,
6929
+ detail: { errors: this.errors.map(({ label, message }) => ({ label, message })) }
6930
+ }));
6931
+ }
6932
+ _onReset() {
6933
+ requestAnimationFrame(() => this.clear());
6934
+ }
6935
+ /** Re-validate a single field once it already had an error, never before. */
6936
+ _onFieldChange(event) {
6937
+ const field = event.target;
6938
+ if (!field || !this._errors.some((error) => error.field === field)) return;
6939
+ if (!field.checkValidity()) return;
6940
+ this._clearFieldError(field);
6941
+ this._errors = this._errors.filter((error) => error.field !== field);
6942
+ this._render();
6943
+ if (this._errors.length === 0) {
6944
+ this.dispatchEvent(new CustomEvent("velin-form-valid", { bubbles: true }));
6945
+ }
6946
+ }
6947
+ // ── Field state ────────────────────────────────────────────────────────────
6948
+ /** @param {HTMLElement} field */
6949
+ _errorId(field) {
6950
+ if (!field.id) field.id = `velin-field-${++fieldIdCounter}`;
6951
+ return `${field.id}-error`;
6952
+ }
6953
+ /**
6954
+ * @param {HTMLElement} field
6955
+ * @param {string} message
6956
+ */
6957
+ _markFieldError(field, message) {
6958
+ const errorId = this._errorId(field);
6959
+ field.setAttribute("aria-invalid", "true");
6960
+ let holder = this.ownerDocument.getElementById(errorId);
6961
+ if (!holder) {
6962
+ holder = this._form.querySelector(`[data-velin-error-for="${escapeSelector(field.name || field.id)}"]`);
6963
+ }
6964
+ if (!holder) {
6965
+ holder = this.ownerDocument.createElement("p");
6966
+ holder.dataset.velinErrorGenerated = "true";
6967
+ field.insertAdjacentElement("afterend", holder);
6968
+ }
6969
+ holder.id = errorId;
6970
+ holder.classList.add("velin-field-error");
6971
+ holder.textContent = message;
6972
+ addDescribedBy(field, errorId);
6973
+ }
6974
+ /** @param {HTMLElement} field */
6975
+ _clearFieldError(field) {
6976
+ if (!field.id) return;
6977
+ const errorId = `${field.id}-error`;
6978
+ field.removeAttribute("aria-invalid");
6979
+ removeDescribedBy(field, errorId);
6980
+ const holder = this.ownerDocument.getElementById(errorId);
6981
+ if (!holder) return;
6982
+ if (holder.dataset.velinErrorGenerated) holder.remove();
6983
+ else holder.textContent = "";
6984
+ }
6985
+ /** @param {HTMLElement} field */
6986
+ _focusField(field) {
6987
+ const target = field.type === "radio" && field.name ? this._form.querySelector(`input[type="radio"][name="${escapeSelector(field.name)}"]`) || field : field;
6988
+ target.focus();
6989
+ this.dispatchEvent(new CustomEvent("velin-form-error-focus", {
6990
+ bubbles: true,
6991
+ detail: { name: target.name || target.id }
6992
+ }));
6993
+ }
6994
+ // ── Summary panel ──────────────────────────────────────────────────────────
6995
+ _render() {
6996
+ if (!this._errors.length) {
6997
+ this._panel?.remove();
6998
+ this._panel = null;
6999
+ this.hidden = true;
7000
+ return;
7001
+ }
7002
+ this.hidden = false;
7003
+ if (!this._panel?.isConnected) {
7004
+ const panel = this.ownerDocument.createElement("div");
7005
+ panel.className = "velin-form-summary__panel velin-alert velin-alert--danger";
7006
+ panel.setAttribute("role", "alert");
7007
+ panel.tabIndex = -1;
7008
+ const heading = this.ownerDocument.createElement("p");
7009
+ heading.className = "velin-form-summary__heading";
7010
+ heading.textContent = this.headingText;
7011
+ const list2 = this.ownerDocument.createElement("ul");
7012
+ list2.className = "velin-form-summary__list";
7013
+ panel.append(heading, list2);
7014
+ this.appendChild(panel);
7015
+ this._panel = panel;
7016
+ }
7017
+ const list = this._panel.querySelector(".velin-form-summary__list");
7018
+ list.textContent = "";
7019
+ for (const error of this._errors) {
7020
+ const item = this.ownerDocument.createElement("li");
7021
+ const link = this.ownerDocument.createElement("a");
7022
+ link.href = `#${this._errorId(error.field).replace(/-error$/, "")}`;
7023
+ link.textContent = `${error.label}: ${error.message}`;
7024
+ link.addEventListener("click", (event) => {
7025
+ event.preventDefault();
7026
+ this._focusField(error.field);
7027
+ });
7028
+ item.appendChild(link);
7029
+ list.appendChild(item);
7030
+ }
7031
+ }
7032
+ _focusPanel() {
7033
+ this._panel?.focus();
7034
+ }
7035
+ _announceErrors() {
7036
+ const count = this._errors.length;
7037
+ announce(count === 1 ? "1 field needs attention" : `${count} fields need attention`, "assertive");
7038
+ }
7039
+ };
7040
+ customElements.define("velin-form-summary", VelinFormSummary);
7041
+ velin_form_summary_default = VelinFormSummary;
7042
+ }
7043
+ });
7044
+
6311
7045
  // components/velin-live-dot.js
6312
7046
  var velin_live_dot_exports = {};
6313
7047
  __export(velin_live_dot_exports, {
@@ -6701,7 +7435,8 @@
6701
7435
  });
6702
7436
  function createSearchWorker(workerUrl) {
6703
7437
  if (typeof Worker === "undefined") return null;
6704
- const url = workerUrl || new URL("./worker.js", import_meta.url).href;
7438
+ if (!workerUrl) return null;
7439
+ const url = workerUrl;
6705
7440
  let worker;
6706
7441
  try {
6707
7442
  worker = new Worker(url, { type: "module" });
@@ -6740,10 +7475,9 @@
6740
7475
  }
6741
7476
  };
6742
7477
  }
6743
- var import_meta, _nextId;
7478
+ var _nextId;
6744
7479
  var init_worker_client = __esm({
6745
7480
  "core/search/worker-client.js"() {
6746
- import_meta = {};
6747
7481
  _nextId = 0;
6748
7482
  }
6749
7483
  });
@@ -7405,10 +8139,12 @@
7405
8139
  "velin-copy": () => Promise.resolve().then(() => (init_velin_copy(), velin_copy_exports)),
7406
8140
  "velin-countdown": () => Promise.resolve().then(() => (init_velin_countdown(), velin_countdown_exports)),
7407
8141
  "velin-counter": () => Promise.resolve().then(() => (init_velin_counter(), velin_counter_exports)),
8142
+ "velin-data-table": () => Promise.resolve().then(() => (init_velin_data_table(), velin_data_table_exports)),
7408
8143
  "velin-dialog": () => Promise.resolve().then(() => (init_velin_dialog(), velin_dialog_exports)),
7409
8144
  "velin-drawer": () => Promise.resolve().then(() => (init_velin_drawer(), velin_drawer_exports)),
7410
8145
  "velin-dropdown": () => Promise.resolve().then(() => (init_velin_dropdown(), velin_dropdown_exports)),
7411
8146
  "velin-email": () => Promise.resolve().then(() => (init_velin_email(), velin_email_exports)),
8147
+ "velin-form-summary": () => Promise.resolve().then(() => (init_velin_form_summary(), velin_form_summary_exports)),
7412
8148
  "velin-icon": () => Promise.resolve().then(() => (init_velin_icon(), velin_icon_exports)),
7413
8149
  "velin-lightbox": () => Promise.resolve().then(() => (init_velin_lightbox(), velin_lightbox_exports)),
7414
8150
  "velin-live-dot": () => Promise.resolve().then(() => (init_velin_live_dot(), velin_live_dot_exports)),
@@ -8024,6 +8760,8 @@
8024
8760
  init_velin_counter();
8025
8761
  init_velin_live_dot();
8026
8762
  init_velin_code_block();
8763
+ init_velin_data_table();
8764
+ init_velin_form_summary();
8027
8765
  init_velin_search();
8028
8766
  init_velin_search();
8029
8767