@prairielearn/browser-utils 1.1.14 → 2.0.1

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.
@@ -1,10 +0,0 @@
1
- CLI Building entry: src/index.ts
2
- CLI Using tsconfig: tsconfig.json
3
- CLI tsup v8.0.2
4
- CLI Target: es2022
5
- CJS Build start
6
- ESM Build start
7
- ESM dist/index.mjs 2.95 KB
8
- ESM ⚡️ Build success in 94ms
9
- CJS dist/index.js 4.22 KB
10
- CJS ⚡️ Build success in 95ms
package/CHANGELOG.md CHANGED
@@ -1,5 +1,24 @@
1
1
  # @prairielearn/browser-utils
2
2
 
3
+ ## 2.0.1
4
+
5
+ ### Patch Changes
6
+
7
+ - 901fce8: Upgrade all JavaScript dependencies
8
+ - Updated dependencies [901fce8]
9
+ - @prairielearn/html@4.0.1
10
+
11
+ ## 2.0.0
12
+
13
+ ### Major Changes
14
+
15
+ - 4f30b7e: Publish as native ESM
16
+
17
+ ### Patch Changes
18
+
19
+ - Updated dependencies [4f30b7e]
20
+ - @prairielearn/html@4.0.0
21
+
3
22
  ## 1.1.14
4
23
 
5
24
  ### 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;AAE3C,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';\n\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
- "use strict";
2
- var __defProp = Object.defineProperty;
3
- var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
- var __getOwnPropNames = Object.getOwnPropertyNames;
5
- var __hasOwnProp = Object.prototype.hasOwnProperty;
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": "1.1.14",
3
+ "version": "2.0.1",
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 && tsup --format cjs,esm src/index.ts",
21
- "dev": "tsup --watch src/index.ts"
13
+ "build": "tsc",
14
+ "dev": "tsc --watch --preserveWatchOutput"
22
15
  },
23
16
  "dependencies": {
24
- "@prairielearn/html": "^3.1.7",
17
+ "@prairielearn/html": "^4.0.1",
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
- "typescript": "^5.4.3"
22
+ "typescript": "^5.4.5"
31
23
  }
32
24
  }
@@ -1,4 +1,5 @@
1
1
  import { encode, decode } from 'js-base64';
2
+
2
3
  import { html, unsafeHtml, HtmlSafeString } from '@prairielearn/html';
3
4
 
4
5
  /**
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
- };