@cocreate/print 1.4.0 → 1.6.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/CONTRIBUTING.md CHANGED
@@ -13,7 +13,7 @@ Fork the [project on Github](https://github.com/CoCreate-app/CoCreate-print) and
13
13
  ```
14
14
  git clone https://github.com/contributor/CoCreate-print.git
15
15
  cd CoCreate-print
16
- git remote add upstream https://github.com/CoCreate-app/CoCreate-print.git
16
+ git remote add upstream https://gitlab.com/cocreatejs/CoCreate-print.git
17
17
  ```
18
18
 
19
19
  ## Create a Topic Branch
package/README.md CHANGED
@@ -78,6 +78,20 @@ We want this library to be community-driven, and CoCreate led. We need your help
78
78
 
79
79
  We appreciate your continued support, thank you!
80
80
 
81
+ ## Print actions
82
+
83
+ Use `actions="print"` with `print-query` to print selected elements. Without an
84
+ `actions` attribute, `print-query` also works as a standalone click control.
85
+ Repeated initialization does not attach duplicate click handlers.
86
+
87
+ Printing copies current input, checkbox, textarea, and select values into an
88
+ isolated iframe, loads the page styles and images, and expands scroll containers
89
+ without changing the live view. The `printed` event is emitted from the trigger
90
+ and bubbles after the browser's print dialog closes (`afterprint`); it does not
91
+ indicate that the user actually printed rather than cancelled. Action chains
92
+ continue at that point. Failures emit `printError` and abort the chain. The print
93
+ iframe is removed on completion or failure.
94
+
81
95
  # License
82
96
 
83
97
  [The MIT License (MIT)](https://github.com/CoCreate-app/CoCreate-print/blob/master/LICENSE)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cocreate/print",
3
- "version": "1.4.0",
3
+ "version": "1.6.0",
4
4
  "description": "A simple print component in vanilla javascript. Easily configured using HTML5 data-attributes and/or JavaScript API.",
5
5
  "keywords": [
6
6
  "print",
@@ -22,7 +22,7 @@
22
22
  },
23
23
  "repository": {
24
24
  "type": "git",
25
- "url": "git+https://github.com/CoCreate-app/CoCreate-print.git"
25
+ "url": "git+https://gitlab.com/cocreatejs/CoCreate-print.git"
26
26
  },
27
27
  "author": "CoCreate LLC",
28
28
  "license": "MIT",
@@ -38,11 +38,11 @@
38
38
  "dependencies": {
39
39
  "@cocreate/actions": "^1.24.0",
40
40
  "@cocreate/element-prototype": "^1.35.0",
41
- "@cocreate/observer": "^1.22.0",
42
- "@cocreate/utils": "^1.52.0"
41
+ "@cocreate/observer": "^1.23.0",
42
+ "@cocreate/utils": "^1.54.0"
43
43
  },
44
44
  "devDependencies": {
45
- "@cocreate/webpack": "^1.8.0"
45
+ "@cocreate/webpack": "^1.9.0"
46
46
  },
47
47
  "allowScripts": {
48
48
  "@cocreate/actions@1.21.4": true,
package/src/index.js CHANGED
@@ -1,153 +1,150 @@
1
- /********************************************************************************
2
- * Copyright (C) 2020 CoCreate LLC and others.
3
- *
4
- *
5
- * SPDX-License-Identifier: MIT
6
- ********************************************************************************/
7
-
8
- import "@cocreate/element-prototype";
9
- import { queryElements } from "@cocreate/utils";
10
- import Actions from "@cocreate/actions";
11
- import Observer from "@cocreate/observer";
12
-
13
- const selector = "[print-query]";
14
-
15
- function init(element) {
16
- if (!element) {
17
- element = document.querySelectorAll(selector);
18
- for (let i = 0; i < element.length; i++) {
19
- initElement(element[i]);
20
- }
21
- } else {
22
- if (
23
- !(element instanceof HTMLCollection) &&
24
- !(element instanceof NodeList) &&
25
- !Array.isArray(element)
26
- ) {
27
- element = [element];
28
- }
29
- for (let i = 0; i < element.length; i++) {
30
- if (element[i].matches(selector)) {
31
- initElement(element[i]);
32
- }
33
- }
34
- }
35
- }
36
-
37
- function initElement(element) {
38
- // Add a click event listener to trigger the print action
39
- element.addEventListener("click", () => {
40
- let actions = element.getAttribute("actions");
41
- if (actions && !actions.includes("print")) {
42
- queryPrintElement(element);
43
- }
44
- });
45
- }
46
-
47
- function queryPrintElement(element) {
48
- let elements = [];
49
- if (element.hasAttribute("print-query")) {
50
- elements = queryElements({ element, prefix: "print" });
51
- } else {
52
- elements = [element];
53
- }
54
- print(elements);
55
- }
56
-
57
- function print(elements) {
58
- if (!elements) return;
59
- if (
60
- !(elements instanceof HTMLCollection) &&
61
- !(elements instanceof NodeList) &&
62
- !Array.isArray(elements)
63
- ) {
64
- elements = [elements];
65
- }
66
-
67
- let content = "";
68
- elements.forEach((target) => {
69
- content += target.outerHTML; // Combine all target elements' HTML
70
- });
71
-
72
- // Include CSS styles from the current window
73
- const styles = Array.from(document.styleSheets)
74
- .map((styleSheet) => {
75
- try {
76
- return Array.from(styleSheet.cssRules)
77
- .map((rule) => rule.cssText)
78
- .join("");
79
- } catch (e) {
80
- console.warn("Could not read stylesheet:", styleSheet.href);
81
- return ""; // Handle CORS issues
82
- }
83
- })
84
- .join("");
85
-
86
- // Create a hidden iframe for printing
87
- const iframe = document.createElement("iframe");
88
- iframe.style.position = "absolute";
89
- iframe.style.top = "-9999px";
90
- iframe.style.left = "-9999px";
91
- document.body.appendChild(iframe);
92
-
93
- // Write content to the iframe
94
- const iframeDoc = iframe.contentDocument || iframe.contentWindow.document;
95
- iframeDoc.open();
96
- iframeDoc.write(`
97
- <html>
98
- <head>
99
- <title>Print</title>
100
- <style>${styles}</style>
101
- </head>
102
- <body>${content}</body>
103
- </html>
104
- `);
105
- iframeDoc.close();
106
-
107
- // Wait for iframe content to load before triggering print
108
- iframe.onload = () => {
109
- iframe.contentWindow.print();
110
-
111
- // Remove iframe after printing
112
- iframe.parentNode.removeChild(iframe);
113
- };
114
-
115
- document.dispatchEvent(
116
- new CustomEvent("printed", {
117
- detail: { content }
118
- })
119
- );
120
- }
121
-
122
- init();
123
-
124
- Actions.init([
125
- {
126
- name: "print",
127
- endEvent: "printed",
128
- callback: (action) => {
129
- queryPrintElement(action.element);
130
- }
131
- }
132
- ]);
133
-
134
- Observer.init({
135
- name: "CoCreatePrintAddedNodes",
136
- types: ["addedNodes"],
137
- selector,
138
- callback(mutation) {
139
- initElement(mutation.target);
140
- }
141
- });
142
-
143
- Observer.init({
144
- name: "CoCreatePrintObserver",
145
- types: ["attributes"],
146
- attributeFilter: ["print-query"],
147
- selector,
148
- callback: function (mutation) {
149
- initElement(mutation.target);
150
- }
151
- });
152
-
153
- export default { init, print };
1
+ /********************************************************************************
2
+ * Copyright (C) 2020 CoCreate LLC and others.
3
+ *
4
+ *
5
+ * SPDX-License-Identifier: MIT
6
+ ********************************************************************************/
7
+
8
+ import "@cocreate/element-prototype";
9
+ import { queryElements } from "@cocreate/utils";
10
+ import Actions from "@cocreate/actions";
11
+ import Observer from "@cocreate/observer";
12
+
13
+ const selector = "[print-query]";
14
+ const initialized = new WeakSet();
15
+
16
+ function init(element) {
17
+ const elements = !element ? document.querySelectorAll(selector)
18
+ : element instanceof Element ? [element] : element;
19
+ for (const target of elements) {
20
+ if (!target.matches(selector) || initialized.has(target)) continue;
21
+ initialized.add(target);
22
+ target.addEventListener("click", () => {
23
+ if (target.hasAttribute("actions")) return;
24
+ runPrint({ element: target });
25
+ });
26
+ }
27
+ }
28
+
29
+ function cloneForPrint(target) {
30
+ const clone = target.cloneNode(true);
31
+ const originals = [target, ...target.querySelectorAll("*")];
32
+ const copies = [clone, ...clone.querySelectorAll("*")];
33
+ originals.forEach((original, index) => {
34
+ const copy = copies[index];
35
+ const style = getComputedStyle(original);
36
+ // Unclip scrollable layouts without showing intentionally hidden content.
37
+ if (/(auto|scroll|hidden|clip)/.test(`${style.overflowX} ${style.overflowY}`)) {
38
+ for (const [name, value] of Object.entries({ overflow: "visible", height: "auto", "max-height": "none", "min-height": "0" }))
39
+ copy.style.setProperty(name, value, "important");
40
+ }
41
+ if (style.position === "sticky") copy.style.setProperty("position", "static", "important");
42
+ if (original.matches("input")) {
43
+ if (original.type !== "file") copy.setAttribute("value", original.value);
44
+ copy.toggleAttribute("checked", original.checked);
45
+ copy.indeterminate = original.indeterminate;
46
+ } else if (original.matches("textarea")) copy.textContent = original.value;
47
+ else if (original.matches("option")) copy.toggleAttribute("selected", original.selected);
48
+ for (const attribute of [...copy.attributes]) {
49
+ if (attribute.name.startsWith("on")) copy.removeAttribute(attribute.name);
50
+ }
51
+ });
52
+ const style = getComputedStyle(target);
53
+ for (const property of style) {
54
+ if (property.startsWith("--")) clone.style.setProperty(property, style.getPropertyValue(property));
55
+ }
56
+ clone.querySelectorAll("script, iframe, object, embed, .simplebar-track, .simplebar-placeholder").forEach(node => node.remove());
57
+ return clone;
58
+ }
59
+
60
+ async function print(elements) {
61
+ if (!elements) return;
62
+ const targets = elements instanceof Element ? [elements] : Array.from(elements);
63
+ if (!targets.length) return;
64
+ const iframe = document.createElement("iframe");
65
+ iframe.title = "Print preview";
66
+ iframe.setAttribute("aria-hidden", "true");
67
+ iframe.style.cssText = "position:fixed;left:-10000px;top:0;width:1000px;height:800px;border:0";
68
+ document.body.appendChild(iframe);
69
+ const frame = iframe.contentWindow;
70
+ const doc = iframe.contentDocument;
71
+ try {
72
+ doc.open();
73
+ doc.write("<!doctype html><html><head><title>Print</title></head><body></body></html>");
74
+ doc.close();
75
+ for (const attribute of document.documentElement.attributes)
76
+ doc.documentElement.setAttribute(attribute.name, attribute.value);
77
+ for (const attribute of document.body.attributes)
78
+ doc.body.setAttribute(attribute.name, attribute.value);
79
+ const base = doc.createElement("base");
80
+ base.href = document.baseURI;
81
+ doc.head.appendChild(base);
82
+ const loading = [];
83
+ for (const sheet of document.querySelectorAll('style, link[rel="stylesheet"]')) {
84
+ const copy = sheet.cloneNode(true);
85
+ if (copy.tagName === "LINK") {
86
+ loading.push(new Promise(resolve => {
87
+ copy.addEventListener("load", resolve, { once: true });
88
+ copy.addEventListener("error", resolve, { once: true });
89
+ }));
90
+ }
91
+ doc.head.appendChild(copy);
92
+ }
93
+ const styles = doc.createElement("style");
94
+ styles.textContent = `
95
+ html, body { height: auto !important; overflow: visible !important; }
96
+ .simplebar-wrapper, .simplebar-mask, .simplebar-offset,
97
+ .simplebar-content-wrapper, .simplebar-content {
98
+ position: static !important; overflow: visible !important;
99
+ height: auto !important; max-height: none !important;
100
+ width: auto !important; margin: 0 !important; transform: none !important;
101
+ }
102
+ thead { display: table-header-group; }
103
+ tr, img { break-inside: avoid; }
104
+ `;
105
+ doc.head.appendChild(styles);
106
+ for (const target of targets) doc.body.appendChild(doc.adoptNode(cloneForPrint(target)));
107
+ for (const img of doc.images) {
108
+ img.loading = "eager";
109
+ if (!img.complete) loading.push(new Promise(resolve => {
110
+ img.addEventListener("load", resolve, { once: true });
111
+ img.addEventListener("error", resolve, { once: true });
112
+ }));
113
+ }
114
+ await Promise.all(loading);
115
+ if (doc.fonts?.ready) await doc.fonts.ready;
116
+ await new Promise(resolve => frame.requestAnimationFrame(() => frame.requestAnimationFrame(resolve)));
117
+ await new Promise((resolve, reject) => {
118
+ frame.addEventListener("afterprint", resolve, { once: true });
119
+ try { frame.focus(); frame.print(); } catch (error) { reject(error); }
120
+ });
121
+ } finally {
122
+ iframe.remove();
123
+ }
124
+ }
125
+
126
+ async function runPrint(action) {
127
+ try {
128
+ const elements = action.element.hasAttribute("print-query")
129
+ ? queryElements({ element: action.element, prefix: "print" }) : [action.element];
130
+ await print(elements);
131
+ if (action.end) action.end();
132
+ else action.element.dispatchEvent(new CustomEvent("printed", { bubbles: true }));
133
+ } catch (error) {
134
+ action.abort?.();
135
+ action.element.dispatchEvent(new CustomEvent("printError", { bubbles: true, detail: { error } }));
136
+ }
137
+ }
138
+
139
+ init();
140
+ Actions.init([{ name: "print", endEvent: "printed", callback: runPrint }]);
141
+ Observer.init({
142
+ name: "CoCreatePrintAddedNodes", types: ["addedNodes"], selector,
143
+ callback: mutation => init(mutation.target)
144
+ });
145
+ Observer.init({
146
+ name: "CoCreatePrintObserver", types: ["attributes"], attributeFilter: ["print-query"], selector,
147
+ callback: mutation => init(mutation.target)
148
+ });
149
+
150
+ export default { init, print };