@gridkitjs/dom 0.2.0 → 0.3.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.
package/dist/index.js CHANGED
@@ -66,6 +66,153 @@ function bodyRowFrom(target) {
66
66
  };
67
67
  }
68
68
 
69
+ // src/checklist/attachChecklist.ts
70
+ var ITEM = "[data-gridkit-checklist-item]";
71
+ var INDEX = "data-gridkit-checklist-index";
72
+ function indexOf(item) {
73
+ return Number(item.getAttribute(INDEX));
74
+ }
75
+ function attachChecklist(list, options) {
76
+ let count = 0;
77
+ let active = 0;
78
+ let pending = null;
79
+ function items() {
80
+ return Array.from(list.querySelectorAll(ITEM));
81
+ }
82
+ function itemAt(index) {
83
+ return list.querySelector(
84
+ `${ITEM}[${INDEX}="${String(index)}"]`
85
+ );
86
+ }
87
+ function itemOf(target) {
88
+ const item = target instanceof Element ? target.closest(ITEM) : null;
89
+ return item !== null && list.contains(item) ? item : null;
90
+ }
91
+ function setTabStop() {
92
+ const all = items();
93
+ const stop = all.find((item) => indexOf(item) === active) ?? all[0];
94
+ for (const item of all) {
95
+ item.tabIndex = item === stop ? 0 : -1;
96
+ }
97
+ }
98
+ function makeActive(index) {
99
+ if (index !== active) {
100
+ active = index;
101
+ options.onFocusChange?.(index);
102
+ }
103
+ }
104
+ function moveTo(index) {
105
+ if (count === 0) {
106
+ return;
107
+ }
108
+ const target = Math.max(0, Math.min(index, count - 1));
109
+ makeActive(target);
110
+ setTabStop();
111
+ options.scrollToIndex(target);
112
+ const item = itemAt(target);
113
+ if (item === null) {
114
+ pending = target;
115
+ } else {
116
+ pending = null;
117
+ item.focus({ preventScroll: true });
118
+ }
119
+ }
120
+ function pageSize() {
121
+ const top = list.getBoundingClientRect().top + list.clientTop;
122
+ const bottom = top + list.clientHeight;
123
+ const whole = items().filter((item) => {
124
+ const box = item.getBoundingClientRect();
125
+ return box.top >= top && box.bottom <= bottom;
126
+ }).length;
127
+ return Math.max(1, whole);
128
+ }
129
+ function onKeyDown2(event) {
130
+ const item = itemOf(event.target);
131
+ if (item === null || event.isComposing || event.altKey || event.ctrlKey || event.metaKey || event.shiftKey) {
132
+ return;
133
+ }
134
+ switch (event.key) {
135
+ case "ArrowDown":
136
+ moveTo(active + 1);
137
+ break;
138
+ case "ArrowUp":
139
+ moveTo(active - 1);
140
+ break;
141
+ case "Home":
142
+ moveTo(0);
143
+ break;
144
+ case "End":
145
+ moveTo(count - 1);
146
+ break;
147
+ case "PageDown":
148
+ moveTo(active + pageSize());
149
+ break;
150
+ case "PageUp":
151
+ moveTo(active - pageSize());
152
+ break;
153
+ case " ":
154
+ if (!event.repeat) {
155
+ options.onToggle(indexOf(item));
156
+ }
157
+ break;
158
+ default:
159
+ return;
160
+ }
161
+ event.preventDefault();
162
+ }
163
+ function onFocusIn(event) {
164
+ const item = itemOf(event.target);
165
+ if (item !== null) {
166
+ pending = null;
167
+ makeActive(indexOf(item));
168
+ setTabStop();
169
+ }
170
+ }
171
+ function onClick(event) {
172
+ const item = itemOf(event.target);
173
+ if (item !== null) {
174
+ options.onToggle(indexOf(item));
175
+ item.focus({ preventScroll: true });
176
+ }
177
+ }
178
+ const observer = new MutationObserver(() => {
179
+ setTabStop();
180
+ const item = pending === null ? null : itemAt(pending);
181
+ if (item === null) {
182
+ return;
183
+ }
184
+ pending = null;
185
+ const focused = document.activeElement;
186
+ if (focused === null || focused === document.body || list.contains(focused)) {
187
+ item.focus({ preventScroll: true });
188
+ }
189
+ });
190
+ observer.observe(list, {
191
+ childList: true,
192
+ subtree: true,
193
+ attributeFilter: [INDEX]
194
+ });
195
+ setTabStop();
196
+ list.addEventListener("keydown", onKeyDown2);
197
+ list.addEventListener("focusin", onFocusIn);
198
+ list.addEventListener("click", onClick);
199
+ return {
200
+ setCount(next) {
201
+ count = next;
202
+ active = Math.max(0, Math.min(active, count - 1));
203
+ setTabStop();
204
+ },
205
+ focusItem: moveTo,
206
+ detach() {
207
+ pending = null;
208
+ observer.disconnect();
209
+ list.removeEventListener("keydown", onKeyDown2);
210
+ list.removeEventListener("focusin", onFocusIn);
211
+ list.removeEventListener("click", onClick);
212
+ }
213
+ };
214
+ }
215
+
69
216
  // src/contextMenu/attachContextMenuTrigger.ts
70
217
  var LONG_PRESS_DELAY = 500;
71
218
  var LONG_PRESS_SLOP = 10;
@@ -249,6 +396,105 @@ function measureColumnContent(table, columnId) {
249
396
  return widest;
250
397
  }
251
398
 
399
+ // src/clipboard.ts
400
+ async function writeItem(clipboard, { text, html }) {
401
+ await clipboard.write([
402
+ new ClipboardItem({
403
+ "text/plain": new Blob([text], { type: "text/plain" }),
404
+ "text/html": new Blob([html], { type: "text/html" })
405
+ })
406
+ ]);
407
+ }
408
+ function copyThroughEvent({ text, html }) {
409
+ const previous = document.activeElement;
410
+ const field = document.createElement("textarea");
411
+ field.value = text;
412
+ field.readOnly = true;
413
+ field.setAttribute("aria-hidden", "true");
414
+ field.style.position = "fixed";
415
+ field.style.top = "0";
416
+ field.style.left = "-9999px";
417
+ document.body.append(field);
418
+ function onCopy(event) {
419
+ if (event.clipboardData === null) {
420
+ return;
421
+ }
422
+ event.clipboardData.setData("text/plain", text);
423
+ event.clipboardData.setData("text/html", html);
424
+ event.preventDefault();
425
+ }
426
+ document.addEventListener("copy", onCopy, { capture: true, once: true });
427
+ try {
428
+ field.focus({ preventScroll: true });
429
+ field.select();
430
+ const legacy = document;
431
+ return legacy.execCommand("copy");
432
+ } finally {
433
+ document.removeEventListener("copy", onCopy, { capture: true });
434
+ field.remove();
435
+ if (previous instanceof HTMLElement) {
436
+ previous.focus({ preventScroll: true });
437
+ }
438
+ }
439
+ }
440
+ var TEXTLESS_INPUTS = /* @__PURE__ */ new Set([
441
+ "button",
442
+ "checkbox",
443
+ "color",
444
+ "file",
445
+ "image",
446
+ "radio",
447
+ "range",
448
+ "reset",
449
+ "submit"
450
+ ]);
451
+ function isTextField(target) {
452
+ return target instanceof HTMLTextAreaElement || target instanceof HTMLInputElement && !TEXTLESS_INPUTS.has(target.type) || target instanceof HTMLElement && target.isContentEditable;
453
+ }
454
+ function isNativeCopy(target) {
455
+ const selection = document.getSelection();
456
+ return isTextField(target) || selection !== null && !selection.isCollapsed;
457
+ }
458
+ async function writeClipboard(content) {
459
+ const attempts = [];
460
+ const clipboard = navigator.clipboard;
461
+ if (clipboard !== void 0) {
462
+ if (typeof ClipboardItem === "function") {
463
+ attempts.push(() => writeItem(clipboard, content));
464
+ }
465
+ attempts.push(() => clipboard.writeText(content.text));
466
+ }
467
+ let failure = null;
468
+ for (const attempt of attempts) {
469
+ try {
470
+ await attempt();
471
+ return;
472
+ } catch (error) {
473
+ failure = error;
474
+ }
475
+ }
476
+ if (!copyThroughEvent(content)) {
477
+ throw new Error("The browser refused to write to the clipboard.", {
478
+ cause: failure
479
+ });
480
+ }
481
+ }
482
+
483
+ // src/download.ts
484
+ function downloadBlob(blob, fileName) {
485
+ const url = URL.createObjectURL(blob);
486
+ const link = document.createElement("a");
487
+ link.href = url;
488
+ link.download = fileName;
489
+ link.hidden = true;
490
+ document.body.append(link);
491
+ link.click();
492
+ link.remove();
493
+ setTimeout(() => {
494
+ URL.revokeObjectURL(url);
495
+ }, 0);
496
+ }
497
+
252
498
  // src/dragReorder.ts
253
499
  var DRAG_THRESHOLD = 4;
254
500
  var NO_DRAG = {
@@ -532,7 +778,7 @@ function attachInfiniteScroll(root, options) {
532
778
 
533
779
  // src/menu/attachMenu.ts
534
780
  import { nextMenuIndex, typeaheadIndex } from "@gridkitjs/core";
535
- var ITEM = [
781
+ var ITEM2 = [
536
782
  '[role="menuitem"][data-gridkit-menu-item]',
537
783
  '[role="menuitemcheckbox"][data-gridkit-menu-item]',
538
784
  '[role="menuitemradio"][data-gridkit-menu-item]'
@@ -568,10 +814,10 @@ function attachMenu(panel, options) {
568
814
  let hovered = null;
569
815
  let hoverTimer;
570
816
  function items() {
571
- return Array.from(panel.querySelectorAll(ITEM));
817
+ return Array.from(panel.querySelectorAll(ITEM2));
572
818
  }
573
819
  function itemOf(target) {
574
- const item = target instanceof Element ? target.closest(ITEM) : null;
820
+ const item = target instanceof Element ? target.closest(ITEM2) : null;
575
821
  return item !== null && panel.contains(item) ? item : null;
576
822
  }
577
823
  function setTabStop(stop) {
@@ -826,6 +1072,11 @@ function placeOverlay(anchor, panel, viewport, options = {}) {
826
1072
 
827
1073
  // src/overlay/attachOverlay.ts
828
1074
  var FOCUSABLE = "a[href], button:not([disabled]), input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]";
1075
+ function tabbables(panel) {
1076
+ return Array.from(panel.querySelectorAll(FOCUSABLE)).filter(
1077
+ (element) => element.tabIndex >= 0 && !element.matches(":disabled") && element.checkVisibility({ visibilityProperty: true })
1078
+ );
1079
+ }
829
1080
  function anchorRect(anchor) {
830
1081
  if (anchor instanceof Element) {
831
1082
  const { top, left, width, height } = anchor.getBoundingClientRect();
@@ -897,6 +1148,28 @@ function attachOverlay(anchor, panel, options) {
897
1148
  onDismiss("blur");
898
1149
  }
899
1150
  }
1151
+ function onTrapKeyDown(event) {
1152
+ if (event.key !== "Tab" || event.defaultPrevented || event.altKey || event.ctrlKey || event.metaKey || !(event.target instanceof Node) || isInsideOverlayAbove(stacked, event.target)) {
1153
+ return;
1154
+ }
1155
+ const stops = tabbables(panel).filter(
1156
+ (element) => !isInsideOverlayAbove(stacked, element)
1157
+ );
1158
+ const first = stops[0];
1159
+ const last = stops.at(-1);
1160
+ if (first === void 0 || last === void 0) {
1161
+ event.preventDefault();
1162
+ return;
1163
+ }
1164
+ const position = event.target.compareDocumentPosition(
1165
+ event.shiftKey ? first : last
1166
+ );
1167
+ const leaving = event.shiftKey ? (position & Node.DOCUMENT_POSITION_PRECEDING) === 0 : (position & Node.DOCUMENT_POSITION_FOLLOWING) === 0;
1168
+ if (leaving) {
1169
+ event.preventDefault();
1170
+ (event.shiftKey ? last : first).focus();
1171
+ }
1172
+ }
900
1173
  function onWindowBlur() {
901
1174
  onDismiss("blur");
902
1175
  }
@@ -937,6 +1210,9 @@ function attachOverlay(anchor, panel, options) {
937
1210
  window.addEventListener("resize", onResize);
938
1211
  window.addEventListener("blur", onWindowBlur);
939
1212
  panel.addEventListener("focusout", onFocusOut);
1213
+ if (options.trapFocus === true) {
1214
+ panel.addEventListener("keydown", onTrapKeyDown);
1215
+ }
940
1216
  if (options.ignoreScrollAfterInput === true) {
941
1217
  panel.addEventListener("input", onInput);
942
1218
  panel.addEventListener("change", onInput);
@@ -956,6 +1232,7 @@ function attachOverlay(anchor, panel, options) {
956
1232
  window.removeEventListener("resize", onResize);
957
1233
  window.removeEventListener("blur", onWindowBlur);
958
1234
  panel.removeEventListener("focusout", onFocusOut);
1235
+ panel.removeEventListener("keydown", onTrapKeyDown);
959
1236
  panel.removeEventListener("input", onInput);
960
1237
  panel.removeEventListener("change", onInput);
961
1238
  if (settlingFrame !== null) {
@@ -1152,7 +1429,7 @@ function attachToolbar(toolbar) {
1152
1429
  }
1153
1430
  );
1154
1431
  }
1155
- function indexOf(all2, node) {
1432
+ function indexOf2(all2, node) {
1156
1433
  return node instanceof Node ? all2.findIndex((control) => control.entry.contains(node)) : -1;
1157
1434
  }
1158
1435
  function setTabStop(next) {
@@ -1167,7 +1444,7 @@ function attachToolbar(toolbar) {
1167
1444
  return;
1168
1445
  }
1169
1446
  const all2 = controls();
1170
- const from = indexOf(all2, event.target);
1447
+ const from = indexOf2(all2, event.target);
1171
1448
  if (from === -1) {
1172
1449
  return;
1173
1450
  }
@@ -1184,7 +1461,7 @@ function attachToolbar(toolbar) {
1184
1461
  }
1185
1462
  function onFocusIn(event) {
1186
1463
  const all2 = controls();
1187
- const control = all2[indexOf(all2, event.target)];
1464
+ const control = all2[indexOf2(all2, event.target)];
1188
1465
  if (control !== void 0) {
1189
1466
  setTabStop(control.target);
1190
1467
  }
@@ -1197,7 +1474,7 @@ function attachToolbar(toolbar) {
1197
1474
  });
1198
1475
  const all = controls();
1199
1476
  setTabStop(
1200
- all[indexOf(all, document.activeElement)]?.target ?? all[0]?.target
1477
+ all[indexOf2(all, document.activeElement)]?.target ?? all[0]?.target
1201
1478
  );
1202
1479
  toolbar.addEventListener("keydown", onKeyDown2);
1203
1480
  toolbar.addEventListener("focusin", onFocusIn);
@@ -1293,8 +1570,139 @@ function observeToolbarOverflow(toolbar, options) {
1293
1570
  }
1294
1571
  };
1295
1572
  }
1573
+
1574
+ // src/print/printDocument.ts
1575
+ var RETURN_EVENTS = ["pointerdown", "keydown"];
1576
+ var printing = false;
1577
+ function createFrame(bodyHtml) {
1578
+ const frame = document.createElement("iframe");
1579
+ frame.setAttribute("aria-hidden", "true");
1580
+ frame.tabIndex = -1;
1581
+ frame.style.position = "fixed";
1582
+ frame.style.right = "0";
1583
+ frame.style.bottom = "0";
1584
+ frame.style.width = "0";
1585
+ frame.style.height = "0";
1586
+ frame.style.border = "0";
1587
+ frame.srcdoc = `<!doctype html><html><head><meta charset="utf-8"></head><body>${bodyHtml}</body></html>`;
1588
+ return frame;
1589
+ }
1590
+ function load(frame) {
1591
+ return new Promise((resolve, reject) => {
1592
+ frame.addEventListener(
1593
+ "load",
1594
+ () => {
1595
+ if (frame.contentWindow === null) {
1596
+ reject(new Error("The print frame has no window."));
1597
+ } else {
1598
+ resolve(frame.contentWindow);
1599
+ }
1600
+ },
1601
+ { once: true }
1602
+ );
1603
+ document.body.append(frame);
1604
+ });
1605
+ }
1606
+ function fill(view, { title, styles }) {
1607
+ const { document: frameDocument } = view;
1608
+ frameDocument.title = title;
1609
+ const style = frameDocument.createElement("style");
1610
+ style.textContent = styles;
1611
+ frameDocument.head.append(style);
1612
+ }
1613
+ function print(view) {
1614
+ const listeners = [
1615
+ [view, "afterprint"],
1616
+ ...RETURN_EVENTS.flatMap((type) => [
1617
+ [view, type],
1618
+ [window, type]
1619
+ ])
1620
+ ];
1621
+ return new Promise((resolve) => {
1622
+ function done() {
1623
+ for (const [target, type] of listeners) {
1624
+ target.removeEventListener(type, done, { capture: true });
1625
+ }
1626
+ resolve();
1627
+ }
1628
+ for (const [target, type] of listeners) {
1629
+ target.addEventListener(type, done, { capture: true });
1630
+ }
1631
+ view.focus();
1632
+ view.print();
1633
+ });
1634
+ }
1635
+ async function printDocument(options) {
1636
+ if (printing) {
1637
+ return;
1638
+ }
1639
+ printing = true;
1640
+ const previous = document.activeElement;
1641
+ const frame = createFrame(options.bodyHtml);
1642
+ try {
1643
+ const view = await load(frame);
1644
+ fill(view, options);
1645
+ await view.document.fonts.ready;
1646
+ await print(view);
1647
+ } finally {
1648
+ frame.remove();
1649
+ printing = false;
1650
+ if (previous instanceof HTMLElement) {
1651
+ previous.focus({ preventScroll: true });
1652
+ }
1653
+ }
1654
+ }
1655
+
1656
+ // src/print/styles.ts
1657
+ var GROUP_INDENT = "12pt";
1658
+ var CELL_PADDING_X = "5pt";
1659
+ var DEEPEST_INDENTED_LEVEL = 6;
1660
+ function pageRules({ paperSize, orientation, pageNumbers }) {
1661
+ const numbers = pageNumbers ? `@bottom-right { content: counter(page) " / " counter(pages); font: 8pt system-ui, sans-serif; color: #555; }` : "";
1662
+ return `@page { size: ${paperSize} ${orientation}; margin: 12mm; ${numbers} }`;
1663
+ }
1664
+ function indentRule(selector, level) {
1665
+ return `${selector} > td:first-child { padding-left: calc(${CELL_PADDING_X} + ${String(level)} * ${GROUP_INDENT}); }`;
1666
+ }
1667
+ function groupIndentRules() {
1668
+ const rules = [
1669
+ indentRule('.export-group:not([data-level="0"])', DEEPEST_INDENTED_LEVEL)
1670
+ ];
1671
+ for (let level = 1; level < DEEPEST_INDENTED_LEVEL; level++) {
1672
+ rules.push(
1673
+ indentRule(`.export-group[data-level="${String(level)}"]`, level)
1674
+ );
1675
+ }
1676
+ return rules.join("\n");
1677
+ }
1678
+ var BASE_RULES = `html { -webkit-print-color-adjust: exact; print-color-adjust: exact; }
1679
+ body { margin: 0; background: #fff; color: #000; font: 10pt/1.35 system-ui, sans-serif; }
1680
+ .gridkit-print-header { margin-bottom: 8pt; }
1681
+ .print-title { margin: 0; font-size: 14pt; }
1682
+ .print-subtitle, .print-timestamp { margin: 2pt 0 0; color: #444; }
1683
+ .gridkit-print-footer { margin-top: 8pt; color: #444; }
1684
+ .gridkit-export-table { width: 100%; border-collapse: collapse; }
1685
+ .gridkit-export-table thead { display: table-header-group; }
1686
+ .gridkit-export-table tfoot { display: table-row-group; }
1687
+ .gridkit-export-table tr { break-inside: avoid; }
1688
+ .gridkit-export-table th, .gridkit-export-table td { border: 1px solid #bbb; padding: 3pt ${CELL_PADDING_X}; text-align: left; vertical-align: top; }
1689
+ .gridkit-export-table th { background: #eee; font-weight: 600; }
1690
+ .export-group > td { background: #f5f5f5; font-weight: 600; }
1691
+ .export-group-summary > td, .gridkit-export-table tfoot td { font-weight: 600; }
1692
+ .gridkit-export-table tfoot td { border-top: 2px solid #888; }`;
1693
+ var FIT_TO_WIDTH_RULES = `.gridkit-export-table { table-layout: fixed; font-size: 8pt; }
1694
+ .gridkit-export-table th, .gridkit-export-table td { overflow-wrap: anywhere; }`;
1695
+ function printStyles(page) {
1696
+ return [
1697
+ pageRules(page),
1698
+ BASE_RULES,
1699
+ groupIndentRules(),
1700
+ page.fitToWidth ? FIT_TO_WIDTH_RULES : ""
1701
+ ].filter((rules) => rules !== "").join("\n");
1702
+ }
1296
1703
  export {
1297
1704
  NO_DRAG,
1705
+ attachChecklist,
1298
1706
  attachContextMenuTrigger,
1299
1707
  attachInfiniteScroll,
1300
1708
  attachMenu,
@@ -1305,16 +1713,21 @@ export {
1305
1713
  createGridFocus,
1306
1714
  createLiveAnnouncer,
1307
1715
  createRowVirtualizer,
1716
+ downloadBlob,
1308
1717
  findBodyRow,
1309
1718
  groupRowFrom,
1719
+ isNativeCopy,
1310
1720
  measureColumnContent,
1311
1721
  measurePageSize,
1312
1722
  observeElementWidth,
1313
1723
  observeToolbarOverflow,
1314
1724
  placeOverlay,
1725
+ printDocument,
1726
+ printStyles,
1315
1727
  resolveColumnDropTarget,
1316
1728
  resolveGroupByDropTarget,
1317
1729
  startColumnResizeGesture,
1318
- startDragReorder
1730
+ startDragReorder,
1731
+ writeClipboard
1319
1732
  };
1320
1733
  //# sourceMappingURL=index.js.map