@prairielearn/browser-utils 1.1.13 → 2.0.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/.turbo/turbo-build.log +0 -10
- package/CHANGELOG.md +18 -0
- package/README.md +0 -4
- package/dist/encode-data.js +31 -0
- package/dist/encode-data.js.map +1 -0
- package/dist/index.d.ts +4 -4
- package/dist/index.js +5 -122
- package/dist/index.js.map +1 -0
- package/dist/on-document-ready.js +11 -0
- package/dist/on-document-ready.js.map +1 -0
- package/dist/parse-html.js +20 -0
- package/dist/parse-html.js.map +1 -0
- package/dist/template-from-attributes.js +59 -0
- package/dist/template-from-attributes.js.map +1 -0
- package/package.json +5 -13
- package/src/index.ts +4 -4
- package/tsconfig.json +1 -4
- package/dist/index.mjs +0 -90
package/.turbo/turbo-build.log
CHANGED
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
[34mCLI[39m Building entry: src/index.ts
|
|
2
|
-
[34mCLI[39m Using tsconfig: tsconfig.json
|
|
3
|
-
[34mCLI[39m tsup v8.0.2
|
|
4
|
-
[34mCLI[39m Target: es2022
|
|
5
|
-
[34mCJS[39m Build start
|
|
6
|
-
[34mESM[39m Build start
|
|
7
|
-
[32mCJS[39m [1mdist/index.js [22m[32m4.22 KB[39m
|
|
8
|
-
[32mCJS[39m ⚡️ Build success in 132ms
|
|
9
|
-
[32mESM[39m [1mdist/index.mjs [22m[32m2.95 KB[39m
|
|
10
|
-
[32mESM[39m ⚡️ Build success in 132ms
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,23 @@
|
|
|
1
1
|
# @prairielearn/browser-utils
|
|
2
2
|
|
|
3
|
+
## 2.0.0
|
|
4
|
+
|
|
5
|
+
### Major Changes
|
|
6
|
+
|
|
7
|
+
- 4f30b7e: Publish as native ESM
|
|
8
|
+
|
|
9
|
+
### Patch Changes
|
|
10
|
+
|
|
11
|
+
- Updated dependencies [4f30b7e]
|
|
12
|
+
- @prairielearn/html@4.0.0
|
|
13
|
+
|
|
14
|
+
## 1.1.14
|
|
15
|
+
|
|
16
|
+
### Patch Changes
|
|
17
|
+
|
|
18
|
+
- Updated dependencies [c7e6553]
|
|
19
|
+
- @prairielearn/html@3.1.7
|
|
20
|
+
|
|
3
21
|
## 1.1.13
|
|
4
22
|
|
|
5
23
|
### Patch Changes
|
package/README.md
CHANGED
|
@@ -90,7 +90,3 @@ document.querySelectorAll('.js-delete-course').forEach((el) => {
|
|
|
90
90
|
});
|
|
91
91
|
});
|
|
92
92
|
```
|
|
93
|
-
|
|
94
|
-
## Development
|
|
95
|
-
|
|
96
|
-
Unlike most other `@prairielearn` packages, this one is built with [`tsup`](https://tsup.egoist.dev/), which is used to generate both CJS and ESM output. The latter is important for ensuring that tree-shaking works correctly when building client bundles, which is important for minimizing bundle sizes.
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { encode, decode } from 'js-base64';
|
|
2
|
+
import { html, unsafeHtml } from '@prairielearn/html';
|
|
3
|
+
/**
|
|
4
|
+
* Use this function as an HTML component encode data that will be passed to the client.
|
|
5
|
+
*
|
|
6
|
+
* @param data The data to encode.
|
|
7
|
+
* @param elementId The element ID to use for the encoded data.
|
|
8
|
+
*
|
|
9
|
+
*/
|
|
10
|
+
export function EncodedData(data, elementId) {
|
|
11
|
+
const encodedData = unsafeHtml(encode(JSON.stringify(data)));
|
|
12
|
+
return html `<script id="${elementId}" type="application/base64">
|
|
13
|
+
${encodedData}
|
|
14
|
+
</script>`;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Decode data that was passed to the client from in HTML component using EncodeData().
|
|
18
|
+
*
|
|
19
|
+
* @param elementId The element ID that stores the encoded data, from from EncodedData().
|
|
20
|
+
* @returns The decoded data.
|
|
21
|
+
*/
|
|
22
|
+
export function decodeData(elementId) {
|
|
23
|
+
const base64Data = document.getElementById(elementId)?.textContent;
|
|
24
|
+
if (base64Data == null) {
|
|
25
|
+
throw new Error(`No data found in element with ID "${elementId}"`);
|
|
26
|
+
}
|
|
27
|
+
const jsonData = decode(base64Data);
|
|
28
|
+
const data = JSON.parse(jsonData);
|
|
29
|
+
return data;
|
|
30
|
+
}
|
|
31
|
+
//# sourceMappingURL=encode-data.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"encode-data.js","sourceRoot":"","sources":["../src/encode-data.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,WAAW,CAAC;AAC3C,OAAO,EAAE,IAAI,EAAE,UAAU,EAAkB,MAAM,oBAAoB,CAAC;AAEtE;;;;;;GAMG;AACH,MAAM,UAAU,WAAW,CAAc,IAAO,EAAE,SAAiB;IACjE,MAAM,WAAW,GAAG,UAAU,CAAC,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAC7D,OAAO,IAAI,CAAA,eAAe,SAAS;MAC/B,WAAW;YACL,CAAC;AACb,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,UAAU,CAAU,SAAiB;IACnD,MAAM,UAAU,GAAG,QAAQ,CAAC,cAAc,CAAC,SAAS,CAAC,EAAE,WAAW,CAAC;IACnE,IAAI,UAAU,IAAI,IAAI,EAAE,CAAC;QACvB,MAAM,IAAI,KAAK,CAAC,qCAAqC,SAAS,GAAG,CAAC,CAAC;IACrE,CAAC;IACD,MAAM,QAAQ,GAAG,MAAM,CAAC,UAAU,CAAC,CAAC;IACpC,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC;IAClC,OAAO,IAAI,CAAC;AACd,CAAC","sourcesContent":["import { encode, decode } from 'js-base64';\nimport { html, unsafeHtml, HtmlSafeString } from '@prairielearn/html';\n\n/**\n * Use this function as an HTML component encode data that will be passed to the client.\n *\n * @param data The data to encode.\n * @param elementId The element ID to use for the encoded data.\n *\n */\nexport function EncodedData<T = unknown>(data: T, elementId: string): HtmlSafeString {\n const encodedData = unsafeHtml(encode(JSON.stringify(data)));\n return html`<script id=\"${elementId}\" type=\"application/base64\">\n ${encodedData}\n </script>`;\n}\n\n/**\n * Decode data that was passed to the client from in HTML component using EncodeData().\n *\n * @param elementId The element ID that stores the encoded data, from from EncodedData().\n * @returns The decoded data.\n */\nexport function decodeData<T = any>(elementId: string): T {\n const base64Data = document.getElementById(elementId)?.textContent;\n if (base64Data == null) {\n throw new Error(`No data found in element with ID \"${elementId}\"`);\n }\n const jsonData = decode(base64Data);\n const data = JSON.parse(jsonData);\n return data;\n}\n"]}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { onDocumentReady } from './on-document-ready';
|
|
2
|
-
export { parseHTML, parseHTMLElement } from './parse-html';
|
|
3
|
-
export { EncodedData, decodeData } from './encode-data';
|
|
4
|
-
export { templateFromAttributes } from './template-from-attributes';
|
|
1
|
+
export { onDocumentReady } from './on-document-ready.js';
|
|
2
|
+
export { parseHTML, parseHTMLElement } from './parse-html.js';
|
|
3
|
+
export { EncodedData, decodeData } from './encode-data.js';
|
|
4
|
+
export { templateFromAttributes } from './template-from-attributes.js';
|
package/dist/index.js
CHANGED
|
@@ -1,122 +1,5 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
var __export = (target, all) => {
|
|
7
|
-
for (var name in all)
|
|
8
|
-
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
-
};
|
|
10
|
-
var __copyProps = (to, from, except, desc) => {
|
|
11
|
-
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
-
for (let key of __getOwnPropNames(from))
|
|
13
|
-
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
-
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
-
}
|
|
16
|
-
return to;
|
|
17
|
-
};
|
|
18
|
-
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
-
|
|
20
|
-
// src/index.ts
|
|
21
|
-
var src_exports = {};
|
|
22
|
-
__export(src_exports, {
|
|
23
|
-
EncodedData: () => EncodedData,
|
|
24
|
-
decodeData: () => decodeData,
|
|
25
|
-
onDocumentReady: () => onDocumentReady,
|
|
26
|
-
parseHTML: () => parseHTML,
|
|
27
|
-
parseHTMLElement: () => parseHTMLElement,
|
|
28
|
-
templateFromAttributes: () => templateFromAttributes
|
|
29
|
-
});
|
|
30
|
-
module.exports = __toCommonJS(src_exports);
|
|
31
|
-
|
|
32
|
-
// src/on-document-ready.ts
|
|
33
|
-
function onDocumentReady(fn) {
|
|
34
|
-
if (document.readyState === "interactive" || document.readyState === "complete") {
|
|
35
|
-
fn();
|
|
36
|
-
} else {
|
|
37
|
-
document.addEventListener("DOMContentLoaded", () => {
|
|
38
|
-
fn();
|
|
39
|
-
});
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
// src/parse-html.ts
|
|
44
|
-
function parseHTML(document2, html2) {
|
|
45
|
-
if (typeof html2 !== "string")
|
|
46
|
-
html2 = html2.toString();
|
|
47
|
-
const template = document2.createElement("template");
|
|
48
|
-
template.innerHTML = html2;
|
|
49
|
-
return document2.importNode(template.content, true);
|
|
50
|
-
}
|
|
51
|
-
function parseHTMLElement(document2, html2) {
|
|
52
|
-
const documentFragment = parseHTML(document2, html2);
|
|
53
|
-
if (documentFragment.childElementCount !== 1) {
|
|
54
|
-
throw new Error("Expected HTML to contain exactly one element");
|
|
55
|
-
}
|
|
56
|
-
return documentFragment.firstElementChild;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
// src/encode-data.ts
|
|
60
|
-
var import_js_base64 = require("js-base64");
|
|
61
|
-
var import_html = require("@prairielearn/html");
|
|
62
|
-
function EncodedData(data, elementId) {
|
|
63
|
-
const encodedData = (0, import_html.unsafeHtml)((0, import_js_base64.encode)(JSON.stringify(data)));
|
|
64
|
-
return import_html.html`<script id="${elementId}" type="application/base64">
|
|
65
|
-
${encodedData}
|
|
66
|
-
</script>`;
|
|
67
|
-
}
|
|
68
|
-
function decodeData(elementId) {
|
|
69
|
-
const base64Data = document.getElementById(elementId)?.textContent;
|
|
70
|
-
if (base64Data == null) {
|
|
71
|
-
throw new Error(`No data found in element with ID "${elementId}"`);
|
|
72
|
-
}
|
|
73
|
-
const jsonData = (0, import_js_base64.decode)(base64Data);
|
|
74
|
-
const data = JSON.parse(jsonData);
|
|
75
|
-
return data;
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
// src/template-from-attributes.ts
|
|
79
|
-
function templateFromAttributes(source, target, attributes) {
|
|
80
|
-
Object.entries(attributes).forEach(([sourceAttribute, targetSelector]) => {
|
|
81
|
-
const attributeValue = source.getAttribute(sourceAttribute);
|
|
82
|
-
if (attributeValue == null) {
|
|
83
|
-
console.error(`Attribute "${sourceAttribute}" not found on source element`);
|
|
84
|
-
return;
|
|
85
|
-
}
|
|
86
|
-
const targets = target.querySelectorAll(targetSelector);
|
|
87
|
-
if (targets.length === 0) {
|
|
88
|
-
console.error(`No elements found matching selector "${targetSelector}"`);
|
|
89
|
-
return;
|
|
90
|
-
}
|
|
91
|
-
targets.forEach((targetElement) => {
|
|
92
|
-
if (targetElement instanceof HTMLInputElement) {
|
|
93
|
-
if (targetElement.type === "checkbox") {
|
|
94
|
-
const attributeParsed = JSON.parse(attributeValue);
|
|
95
|
-
targetElement.checked = !!attributeParsed;
|
|
96
|
-
targetElement.dispatchEvent(new Event("change", { bubbles: true }));
|
|
97
|
-
} else {
|
|
98
|
-
targetElement.value = attributeValue;
|
|
99
|
-
}
|
|
100
|
-
} else if (targetElement instanceof HTMLSelectElement) {
|
|
101
|
-
const i = Array.from(targetElement.options).findIndex((o) => o.value === attributeValue);
|
|
102
|
-
if (i >= 0) {
|
|
103
|
-
targetElement.selectedIndex = i;
|
|
104
|
-
targetElement.dispatchEvent(new Event("change", { bubbles: true }));
|
|
105
|
-
} else {
|
|
106
|
-
console.error(`Could not find option with value "${attributeValue}"`);
|
|
107
|
-
}
|
|
108
|
-
} else {
|
|
109
|
-
targetElement.textContent = attributeValue;
|
|
110
|
-
}
|
|
111
|
-
});
|
|
112
|
-
});
|
|
113
|
-
}
|
|
114
|
-
// Annotate the CommonJS export names for ESM import in node:
|
|
115
|
-
0 && (module.exports = {
|
|
116
|
-
EncodedData,
|
|
117
|
-
decodeData,
|
|
118
|
-
onDocumentReady,
|
|
119
|
-
parseHTML,
|
|
120
|
-
parseHTMLElement,
|
|
121
|
-
templateFromAttributes
|
|
122
|
-
});
|
|
1
|
+
export { onDocumentReady } from './on-document-ready.js';
|
|
2
|
+
export { parseHTML, parseHTMLElement } from './parse-html.js';
|
|
3
|
+
export { EncodedData, decodeData } from './encode-data.js';
|
|
4
|
+
export { templateFromAttributes } from './template-from-attributes.js';
|
|
5
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AACzD,OAAO,EAAE,SAAS,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AAC9D,OAAO,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAC3D,OAAO,EAAE,sBAAsB,EAAE,MAAM,+BAA+B,CAAC","sourcesContent":["export { onDocumentReady } from './on-document-ready.js';\nexport { parseHTML, parseHTMLElement } from './parse-html.js';\nexport { EncodedData, decodeData } from './encode-data.js';\nexport { templateFromAttributes } from './template-from-attributes.js';\n"]}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export function onDocumentReady(fn) {
|
|
2
|
+
if (document.readyState === 'interactive' || document.readyState === 'complete') {
|
|
3
|
+
fn();
|
|
4
|
+
}
|
|
5
|
+
else {
|
|
6
|
+
document.addEventListener('DOMContentLoaded', () => {
|
|
7
|
+
fn();
|
|
8
|
+
});
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
//# sourceMappingURL=on-document-ready.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"on-document-ready.js","sourceRoot":"","sources":["../src/on-document-ready.ts"],"names":[],"mappings":"AAAA,MAAM,UAAU,eAAe,CAAC,EAAc;IAC5C,IAAI,QAAQ,CAAC,UAAU,KAAK,aAAa,IAAI,QAAQ,CAAC,UAAU,KAAK,UAAU,EAAE,CAAC;QAChF,EAAE,EAAE,CAAC;IACP,CAAC;SAAM,CAAC;QACN,QAAQ,CAAC,gBAAgB,CAAC,kBAAkB,EAAE,GAAG,EAAE;YACjD,EAAE,EAAE,CAAC;QACP,CAAC,CAAC,CAAC;IACL,CAAC;AACH,CAAC","sourcesContent":["export function onDocumentReady(fn: () => void): void {\n if (document.readyState === 'interactive' || document.readyState === 'complete') {\n fn();\n } else {\n document.addEventListener('DOMContentLoaded', () => {\n fn();\n });\n }\n}\n"]}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export function parseHTML(document, html) {
|
|
2
|
+
if (typeof html !== 'string')
|
|
3
|
+
html = html.toString();
|
|
4
|
+
const template = document.createElement('template');
|
|
5
|
+
template.innerHTML = html;
|
|
6
|
+
return document.importNode(template.content, true);
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Like {@link parseHTML}, but returns an {@link Element} instead of a
|
|
10
|
+
* {@link DocumentFragment}. If the HTML being parsed does not contain
|
|
11
|
+
* exactly one element, an error is thrown.
|
|
12
|
+
*/
|
|
13
|
+
export function parseHTMLElement(document, html) {
|
|
14
|
+
const documentFragment = parseHTML(document, html);
|
|
15
|
+
if (documentFragment.childElementCount !== 1) {
|
|
16
|
+
throw new Error('Expected HTML to contain exactly one element');
|
|
17
|
+
}
|
|
18
|
+
return documentFragment.firstElementChild;
|
|
19
|
+
}
|
|
20
|
+
//# sourceMappingURL=parse-html.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"parse-html.js","sourceRoot":"","sources":["../src/parse-html.ts"],"names":[],"mappings":"AAEA,MAAM,UAAU,SAAS,CAAC,QAAkB,EAAE,IAA6B;IACzE,IAAI,OAAO,IAAI,KAAK,QAAQ;QAAE,IAAI,GAAG,IAAI,CAAC,QAAQ,EAAE,CAAC;IACrD,MAAM,QAAQ,GAAG,QAAQ,CAAC,aAAa,CAAC,UAAU,CAAC,CAAC;IACpD,QAAQ,CAAC,SAAS,GAAG,IAAI,CAAC;IAC1B,OAAO,QAAQ,CAAC,UAAU,CAAC,QAAQ,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC;AACrD,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,gBAAgB,CAAC,QAAkB,EAAE,IAA6B;IAChF,MAAM,gBAAgB,GAAG,SAAS,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC;IACnD,IAAI,gBAAgB,CAAC,iBAAiB,KAAK,CAAC,EAAE,CAAC;QAC7C,MAAM,IAAI,KAAK,CAAC,8CAA8C,CAAC,CAAC;IAClE,CAAC;IACD,OAAO,gBAAgB,CAAC,iBAA4B,CAAC;AACvD,CAAC","sourcesContent":["import type { HtmlSafeString } from '@prairielearn/html';\n\nexport function parseHTML(document: Document, html: string | HtmlSafeString): DocumentFragment {\n if (typeof html !== 'string') html = html.toString();\n const template = document.createElement('template');\n template.innerHTML = html;\n return document.importNode(template.content, true);\n}\n\n/**\n * Like {@link parseHTML}, but returns an {@link Element} instead of a\n * {@link DocumentFragment}. If the HTML being parsed does not contain\n * exactly one element, an error is thrown.\n */\nexport function parseHTMLElement(document: Document, html: string | HtmlSafeString): Element {\n const documentFragment = parseHTML(document, html);\n if (documentFragment.childElementCount !== 1) {\n throw new Error('Expected HTML to contain exactly one element');\n }\n return documentFragment.firstElementChild as Element;\n}\n"]}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* For each key in `attributes`, copies that attribute's value from `source`
|
|
3
|
+
* into all elements within `target` that match the corresponding value in
|
|
4
|
+
* `attributes`.
|
|
5
|
+
*
|
|
6
|
+
* For `<input type="checkbox">` elements it interprets the attribute as JSON
|
|
7
|
+
* and uses the truthiness of it to set `checked`. For other `<input>` elements,
|
|
8
|
+
* it sets the `value` attribute. For all others, it sets the `textContent`
|
|
9
|
+
* attribute.
|
|
10
|
+
*
|
|
11
|
+
* @param source The element to copy attributes from
|
|
12
|
+
* @param target The element to copy attributes into
|
|
13
|
+
* @param attributes A map of attributes to copy from `source` to `target`
|
|
14
|
+
* @param param.debug If true, logs debug information to the console
|
|
15
|
+
*/
|
|
16
|
+
export function templateFromAttributes(source, target, attributes) {
|
|
17
|
+
Object.entries(attributes).forEach(([sourceAttribute, targetSelector]) => {
|
|
18
|
+
const attributeValue = source.getAttribute(sourceAttribute);
|
|
19
|
+
if (attributeValue == null) {
|
|
20
|
+
console.error(`Attribute "${sourceAttribute}" not found on source element`);
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
const targets = target.querySelectorAll(targetSelector);
|
|
24
|
+
if (targets.length === 0) {
|
|
25
|
+
console.error(`No elements found matching selector "${targetSelector}"`);
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
targets.forEach((targetElement) => {
|
|
29
|
+
if (targetElement instanceof HTMLInputElement) {
|
|
30
|
+
if (targetElement.type === 'checkbox') {
|
|
31
|
+
const attributeParsed = JSON.parse(attributeValue);
|
|
32
|
+
targetElement.checked = !!attributeParsed;
|
|
33
|
+
// Manually trigger a 'change' event. This does not trigger
|
|
34
|
+
// automatically when we change properties like 'checked'.
|
|
35
|
+
targetElement.dispatchEvent(new Event('change', { bubbles: true }));
|
|
36
|
+
}
|
|
37
|
+
else {
|
|
38
|
+
targetElement.value = attributeValue;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
else if (targetElement instanceof HTMLSelectElement) {
|
|
42
|
+
const i = Array.from(targetElement.options).findIndex((o) => o.value === attributeValue);
|
|
43
|
+
if (i >= 0) {
|
|
44
|
+
targetElement.selectedIndex = i;
|
|
45
|
+
// Manually trigger a 'change' event. This does not trigger
|
|
46
|
+
// automatically when we change properties like 'checked'.
|
|
47
|
+
targetElement.dispatchEvent(new Event('change', { bubbles: true }));
|
|
48
|
+
}
|
|
49
|
+
else {
|
|
50
|
+
console.error(`Could not find option with value "${attributeValue}"`);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
else {
|
|
54
|
+
targetElement.textContent = attributeValue;
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
//# sourceMappingURL=template-from-attributes.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"template-from-attributes.js","sourceRoot":"","sources":["../src/template-from-attributes.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;GAcG;AACH,MAAM,UAAU,sBAAsB,CACpC,MAAmB,EACnB,MAAmB,EACnB,UAAwB;IAExB,MAAM,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,eAAe,EAAE,cAAc,CAAC,EAAE,EAAE;QACvE,MAAM,cAAc,GAAG,MAAM,CAAC,YAAY,CAAC,eAAe,CAAC,CAAC;QAC5D,IAAI,cAAc,IAAI,IAAI,EAAE,CAAC;YAC3B,OAAO,CAAC,KAAK,CAAC,cAAc,eAAe,+BAA+B,CAAC,CAAC;YAC5E,OAAO;QACT,CAAC;QAED,MAAM,OAAO,GAAG,MAAM,CAAC,gBAAgB,CAAC,cAAc,CAAC,CAAC;QACxD,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACzB,OAAO,CAAC,KAAK,CAAC,wCAAwC,cAAc,GAAG,CAAC,CAAC;YACzE,OAAO;QACT,CAAC;QAED,OAAO,CAAC,OAAO,CAAC,CAAC,aAAa,EAAE,EAAE;YAChC,IAAI,aAAa,YAAY,gBAAgB,EAAE,CAAC;gBAC9C,IAAI,aAAa,CAAC,IAAI,KAAK,UAAU,EAAE,CAAC;oBACtC,MAAM,eAAe,GAAG,IAAI,CAAC,KAAK,CAAC,cAAc,CAAC,CAAC;oBACnD,aAAa,CAAC,OAAO,GAAG,CAAC,CAAC,eAAe,CAAC;oBAC1C,2DAA2D;oBAC3D,0DAA0D;oBAC1D,aAAa,CAAC,aAAa,CAAC,IAAI,KAAK,CAAC,QAAQ,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC;gBACtE,CAAC;qBAAM,CAAC;oBACN,aAAa,CAAC,KAAK,GAAG,cAAc,CAAC;gBACvC,CAAC;YACH,CAAC;iBAAM,IAAI,aAAa,YAAY,iBAAiB,EAAE,CAAC;gBACtD,MAAM,CAAC,GAAG,KAAK,CAAC,IAAI,CAAC,aAAa,CAAC,OAAO,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,KAAK,cAAc,CAAC,CAAC;gBACzF,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;oBACX,aAAa,CAAC,aAAa,GAAG,CAAC,CAAC;oBAChC,2DAA2D;oBAC3D,0DAA0D;oBAC1D,aAAa,CAAC,aAAa,CAAC,IAAI,KAAK,CAAC,QAAQ,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC;gBACtE,CAAC;qBAAM,CAAC;oBACN,OAAO,CAAC,KAAK,CAAC,qCAAqC,cAAc,GAAG,CAAC,CAAC;gBACxE,CAAC;YACH,CAAC;iBAAM,CAAC;gBACN,aAAa,CAAC,WAAW,GAAG,cAAc,CAAC;YAC7C,CAAC;QACH,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;AACL,CAAC","sourcesContent":["type AttributeMap = Record<string, string>;\n\n/**\n * For each key in `attributes`, copies that attribute's value from `source`\n * into all elements within `target` that match the corresponding value in\n * `attributes`.\n *\n * For `<input type=\"checkbox\">` elements it interprets the attribute as JSON\n * and uses the truthiness of it to set `checked`. For other `<input>` elements,\n * it sets the `value` attribute. For all others, it sets the `textContent`\n * attribute.\n *\n * @param source The element to copy attributes from\n * @param target The element to copy attributes into\n * @param attributes A map of attributes to copy from `source` to `target`\n * @param param.debug If true, logs debug information to the console\n */\nexport function templateFromAttributes(\n source: HTMLElement,\n target: HTMLElement,\n attributes: AttributeMap,\n) {\n Object.entries(attributes).forEach(([sourceAttribute, targetSelector]) => {\n const attributeValue = source.getAttribute(sourceAttribute);\n if (attributeValue == null) {\n console.error(`Attribute \"${sourceAttribute}\" not found on source element`);\n return;\n }\n\n const targets = target.querySelectorAll(targetSelector);\n if (targets.length === 0) {\n console.error(`No elements found matching selector \"${targetSelector}\"`);\n return;\n }\n\n targets.forEach((targetElement) => {\n if (targetElement instanceof HTMLInputElement) {\n if (targetElement.type === 'checkbox') {\n const attributeParsed = JSON.parse(attributeValue);\n targetElement.checked = !!attributeParsed;\n // Manually trigger a 'change' event. This does not trigger\n // automatically when we change properties like 'checked'.\n targetElement.dispatchEvent(new Event('change', { bubbles: true }));\n } else {\n targetElement.value = attributeValue;\n }\n } else if (targetElement instanceof HTMLSelectElement) {\n const i = Array.from(targetElement.options).findIndex((o) => o.value === attributeValue);\n if (i >= 0) {\n targetElement.selectedIndex = i;\n // Manually trigger a 'change' event. This does not trigger\n // automatically when we change properties like 'checked'.\n targetElement.dispatchEvent(new Event('change', { bubbles: true }));\n } else {\n console.error(`Could not find option with value \"${attributeValue}\"`);\n }\n } else {\n targetElement.textContent = attributeValue;\n }\n });\n });\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@prairielearn/browser-utils",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.0",
|
|
4
|
+
"type": "module",
|
|
4
5
|
"main": "dist/index.js",
|
|
5
6
|
"sideEffects": false,
|
|
6
7
|
"repository": {
|
|
@@ -8,25 +9,16 @@
|
|
|
8
9
|
"url": "https://github.com/PrairieLearn/PrairieLearn.git",
|
|
9
10
|
"directory": "packages/browser-utils"
|
|
10
11
|
},
|
|
11
|
-
"exports": {
|
|
12
|
-
"./package.json": "./package.json",
|
|
13
|
-
".": {
|
|
14
|
-
"import": "./dist/index.mjs",
|
|
15
|
-
"require": "./dist/index.js",
|
|
16
|
-
"types": "./dist/index.d.ts"
|
|
17
|
-
}
|
|
18
|
-
},
|
|
19
12
|
"scripts": {
|
|
20
|
-
"build": "tsc
|
|
21
|
-
"dev": "
|
|
13
|
+
"build": "tsc",
|
|
14
|
+
"dev": "tsc --watch --preserveWatchOutput"
|
|
22
15
|
},
|
|
23
16
|
"dependencies": {
|
|
24
|
-
"@prairielearn/html": "^
|
|
17
|
+
"@prairielearn/html": "^4.0.0",
|
|
25
18
|
"js-base64": "^3.7.7"
|
|
26
19
|
},
|
|
27
20
|
"devDependencies": {
|
|
28
21
|
"@prairielearn/tsconfig": "^0.0.0",
|
|
29
|
-
"tsup": "^8.0.2",
|
|
30
22
|
"typescript": "^5.4.3"
|
|
31
23
|
}
|
|
32
24
|
}
|
package/src/index.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { onDocumentReady } from './on-document-ready';
|
|
2
|
-
export { parseHTML, parseHTMLElement } from './parse-html';
|
|
3
|
-
export { EncodedData, decodeData } from './encode-data';
|
|
4
|
-
export { templateFromAttributes } from './template-from-attributes';
|
|
1
|
+
export { onDocumentReady } from './on-document-ready.js';
|
|
2
|
+
export { parseHTML, parseHTMLElement } from './parse-html.js';
|
|
3
|
+
export { EncodedData, decodeData } from './encode-data.js';
|
|
4
|
+
export { templateFromAttributes } from './template-from-attributes.js';
|
package/tsconfig.json
CHANGED
|
@@ -2,9 +2,6 @@
|
|
|
2
2
|
"extends": "@prairielearn/tsconfig/tsconfig.package.json",
|
|
3
3
|
"compilerOptions": {
|
|
4
4
|
"outDir": "./dist",
|
|
5
|
-
"rootDir": "./src"
|
|
6
|
-
// Unlike other PrairieLearn packages, we use another tool to actually
|
|
7
|
-
// emit transpiled JavaScript. See note in `README.md`.
|
|
8
|
-
"emitDeclarationOnly": true
|
|
5
|
+
"rootDir": "./src"
|
|
9
6
|
}
|
|
10
7
|
}
|
package/dist/index.mjs
DELETED
|
@@ -1,90 +0,0 @@
|
|
|
1
|
-
// src/on-document-ready.ts
|
|
2
|
-
function onDocumentReady(fn) {
|
|
3
|
-
if (document.readyState === "interactive" || document.readyState === "complete") {
|
|
4
|
-
fn();
|
|
5
|
-
} else {
|
|
6
|
-
document.addEventListener("DOMContentLoaded", () => {
|
|
7
|
-
fn();
|
|
8
|
-
});
|
|
9
|
-
}
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
// src/parse-html.ts
|
|
13
|
-
function parseHTML(document2, html2) {
|
|
14
|
-
if (typeof html2 !== "string")
|
|
15
|
-
html2 = html2.toString();
|
|
16
|
-
const template = document2.createElement("template");
|
|
17
|
-
template.innerHTML = html2;
|
|
18
|
-
return document2.importNode(template.content, true);
|
|
19
|
-
}
|
|
20
|
-
function parseHTMLElement(document2, html2) {
|
|
21
|
-
const documentFragment = parseHTML(document2, html2);
|
|
22
|
-
if (documentFragment.childElementCount !== 1) {
|
|
23
|
-
throw new Error("Expected HTML to contain exactly one element");
|
|
24
|
-
}
|
|
25
|
-
return documentFragment.firstElementChild;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
// src/encode-data.ts
|
|
29
|
-
import { encode, decode } from "js-base64";
|
|
30
|
-
import { html, unsafeHtml } from "@prairielearn/html";
|
|
31
|
-
function EncodedData(data, elementId) {
|
|
32
|
-
const encodedData = unsafeHtml(encode(JSON.stringify(data)));
|
|
33
|
-
return html`<script id="${elementId}" type="application/base64">
|
|
34
|
-
${encodedData}
|
|
35
|
-
</script>`;
|
|
36
|
-
}
|
|
37
|
-
function decodeData(elementId) {
|
|
38
|
-
const base64Data = document.getElementById(elementId)?.textContent;
|
|
39
|
-
if (base64Data == null) {
|
|
40
|
-
throw new Error(`No data found in element with ID "${elementId}"`);
|
|
41
|
-
}
|
|
42
|
-
const jsonData = decode(base64Data);
|
|
43
|
-
const data = JSON.parse(jsonData);
|
|
44
|
-
return data;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
// src/template-from-attributes.ts
|
|
48
|
-
function templateFromAttributes(source, target, attributes) {
|
|
49
|
-
Object.entries(attributes).forEach(([sourceAttribute, targetSelector]) => {
|
|
50
|
-
const attributeValue = source.getAttribute(sourceAttribute);
|
|
51
|
-
if (attributeValue == null) {
|
|
52
|
-
console.error(`Attribute "${sourceAttribute}" not found on source element`);
|
|
53
|
-
return;
|
|
54
|
-
}
|
|
55
|
-
const targets = target.querySelectorAll(targetSelector);
|
|
56
|
-
if (targets.length === 0) {
|
|
57
|
-
console.error(`No elements found matching selector "${targetSelector}"`);
|
|
58
|
-
return;
|
|
59
|
-
}
|
|
60
|
-
targets.forEach((targetElement) => {
|
|
61
|
-
if (targetElement instanceof HTMLInputElement) {
|
|
62
|
-
if (targetElement.type === "checkbox") {
|
|
63
|
-
const attributeParsed = JSON.parse(attributeValue);
|
|
64
|
-
targetElement.checked = !!attributeParsed;
|
|
65
|
-
targetElement.dispatchEvent(new Event("change", { bubbles: true }));
|
|
66
|
-
} else {
|
|
67
|
-
targetElement.value = attributeValue;
|
|
68
|
-
}
|
|
69
|
-
} else if (targetElement instanceof HTMLSelectElement) {
|
|
70
|
-
const i = Array.from(targetElement.options).findIndex((o) => o.value === attributeValue);
|
|
71
|
-
if (i >= 0) {
|
|
72
|
-
targetElement.selectedIndex = i;
|
|
73
|
-
targetElement.dispatchEvent(new Event("change", { bubbles: true }));
|
|
74
|
-
} else {
|
|
75
|
-
console.error(`Could not find option with value "${attributeValue}"`);
|
|
76
|
-
}
|
|
77
|
-
} else {
|
|
78
|
-
targetElement.textContent = attributeValue;
|
|
79
|
-
}
|
|
80
|
-
});
|
|
81
|
-
});
|
|
82
|
-
}
|
|
83
|
-
export {
|
|
84
|
-
EncodedData,
|
|
85
|
-
decodeData,
|
|
86
|
-
onDocumentReady,
|
|
87
|
-
parseHTML,
|
|
88
|
-
parseHTMLElement,
|
|
89
|
-
templateFromAttributes
|
|
90
|
-
};
|