@prairielearn/browser-utils 2.2.14 → 2.4.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/CHANGELOG.md CHANGED
@@ -1,5 +1,17 @@
1
1
  # @prairielearn/browser-utils
2
2
 
3
+ ## 2.4.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 5b46852: Add `downloadTextFile`, `downloadAsJSON` and `downloadAsCSV` utilities
8
+
9
+ ## 2.3.0
10
+
11
+ ### Minor Changes
12
+
13
+ - 65c63e0: Change the default return type of `decodeData` to unknown
14
+
3
15
  ## 2.2.14
4
16
 
5
17
  ### Patch Changes
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Triggers a browser download of a text-based file.
3
+ *
4
+ * @param content The content of the file.
5
+ * @param filename The desired filename.
6
+ * @param mimeType The MIME type of the file.
7
+ */
8
+ export declare function downloadTextFile(content: string, filename: string, mimeType: string): void;
9
+ /**
10
+ * Triggers a browser download of a JSON file.
11
+ *
12
+ * @param data The data to be included in the JSON file.
13
+ * @param filename The desired filename.
14
+ */
15
+ export declare function downloadAsJSON(data: any, filename: string): void;
16
+ /**
17
+ * Triggers a browser download of a CSV file.
18
+ *
19
+ * @param header The header row of the CSV.
20
+ * @param data The data rows of the CSV.
21
+ * @param filename The desired filename.
22
+ */
23
+ export declare function downloadAsCSV(header: string[], data: unknown[][], filename: string): void;
@@ -0,0 +1,43 @@
1
+ import { stringify } from 'csv-stringify/browser/esm/sync';
2
+ /**
3
+ * Triggers a browser download of a text-based file.
4
+ *
5
+ * @param content The content of the file.
6
+ * @param filename The desired filename.
7
+ * @param mimeType The MIME type of the file.
8
+ */
9
+ export function downloadTextFile(content, filename, mimeType) {
10
+ if (typeof window === 'undefined')
11
+ return;
12
+ const blob = new Blob([content], { type: mimeType });
13
+ const url = URL.createObjectURL(blob);
14
+ const a = document.createElement('a');
15
+ a.href = url;
16
+ a.download = filename;
17
+ document.body.append(a);
18
+ a.click();
19
+ a.remove();
20
+ URL.revokeObjectURL(url);
21
+ }
22
+ /**
23
+ * Triggers a browser download of a JSON file.
24
+ *
25
+ * @param data The data to be included in the JSON file.
26
+ * @param filename The desired filename.
27
+ */
28
+ export function downloadAsJSON(data, filename) {
29
+ const jsonContent = JSON.stringify(data, null, 2);
30
+ downloadTextFile(jsonContent, filename, 'application/json');
31
+ }
32
+ /**
33
+ * Triggers a browser download of a CSV file.
34
+ *
35
+ * @param header The header row of the CSV.
36
+ * @param data The data rows of the CSV.
37
+ * @param filename The desired filename.
38
+ */
39
+ export function downloadAsCSV(header, data, filename) {
40
+ const csvContent = stringify(data, { header: true, columns: header });
41
+ downloadTextFile(csvContent, filename, 'text/csv');
42
+ }
43
+ //# sourceMappingURL=downloads.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"downloads.js","sourceRoot":"","sources":["../src/downloads.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,gCAAgC,CAAC;AAE3D;;;;;;GAMG;AACH,MAAM,UAAU,gBAAgB,CAAC,OAAe,EAAE,QAAgB,EAAE,QAAgB;IAClF,IAAI,OAAO,MAAM,KAAK,WAAW;QAAE,OAAO;IAC1C,MAAM,IAAI,GAAG,IAAI,IAAI,CAAC,CAAC,OAAO,CAAC,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC,CAAC;IACrD,MAAM,GAAG,GAAG,GAAG,CAAC,eAAe,CAAC,IAAI,CAAC,CAAC;IACtC,MAAM,CAAC,GAAG,QAAQ,CAAC,aAAa,CAAC,GAAG,CAAC,CAAC;IACtC,CAAC,CAAC,IAAI,GAAG,GAAG,CAAC;IACb,CAAC,CAAC,QAAQ,GAAG,QAAQ,CAAC;IACtB,QAAQ,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;IACxB,CAAC,CAAC,KAAK,EAAE,CAAC;IACV,CAAC,CAAC,MAAM,EAAE,CAAC;IACX,GAAG,CAAC,eAAe,CAAC,GAAG,CAAC,CAAC;AAC3B,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,cAAc,CAAC,IAAS,EAAE,QAAgB;IACxD,MAAM,WAAW,GAAG,IAAI,CAAC,SAAS,CAAC,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC;IAClD,gBAAgB,CAAC,WAAW,EAAE,QAAQ,EAAE,kBAAkB,CAAC,CAAC;AAC9D,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,aAAa,CAAC,MAAgB,EAAE,IAAiB,EAAE,QAAgB;IACjF,MAAM,UAAU,GAAG,SAAS,CAAC,IAAI,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,EAAE,CAAC,CAAC;IACtE,gBAAgB,CAAC,UAAU,EAAE,QAAQ,EAAE,UAAU,CAAC,CAAC;AACrD,CAAC","sourcesContent":["import { stringify } from 'csv-stringify/browser/esm/sync';\n\n/**\n * Triggers a browser download of a text-based file.\n *\n * @param content The content of the file.\n * @param filename The desired filename.\n * @param mimeType The MIME type of the file.\n */\nexport function downloadTextFile(content: string, filename: string, mimeType: string): void {\n if (typeof window === 'undefined') return;\n const blob = new Blob([content], { type: mimeType });\n const url = URL.createObjectURL(blob);\n const a = document.createElement('a');\n a.href = url;\n a.download = filename;\n document.body.append(a);\n a.click();\n a.remove();\n URL.revokeObjectURL(url);\n}\n\n/**\n * Triggers a browser download of a JSON file.\n *\n * @param data The data to be included in the JSON file.\n * @param filename The desired filename.\n */\nexport function downloadAsJSON(data: any, filename: string): void {\n const jsonContent = JSON.stringify(data, null, 2);\n downloadTextFile(jsonContent, filename, 'application/json');\n}\n\n/**\n * Triggers a browser download of a CSV file.\n *\n * @param header The header row of the CSV.\n * @param data The data rows of the CSV.\n * @param filename The desired filename.\n */\nexport function downloadAsCSV(header: string[], data: unknown[][], filename: string): void {\n const csvContent = stringify(data, { header: true, columns: header });\n downloadTextFile(csvContent, filename, 'text/csv');\n}\n"]}
@@ -13,4 +13,4 @@ export declare function EncodedData<T = unknown>(data: T, elementId: string): Ht
13
13
  * @param elementId The element ID that stores the encoded data, from from EncodedData().
14
14
  * @returns The decoded data.
15
15
  */
16
- export declare function decodeData<T = any>(elementId: string): T;
16
+ export declare function decodeData<T = unknown>(elementId: string): T;
@@ -1 +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,EAAuB,IAAI,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAE3E;;;;;;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 { decode, encode } from 'js-base64';\n\nimport { type HtmlSafeString, html, unsafeHtml } 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"]}
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,EAAuB,IAAI,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAE3E;;;;;;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,CAAc,SAAiB;IACvD,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 { decode, encode } from 'js-base64';\n\nimport { type HtmlSafeString, html, unsafeHtml } 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 = unknown>(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
@@ -3,3 +3,4 @@ export { parseHTML, parseHTMLElement } from './parse-html.js';
3
3
  export { EncodedData, decodeData } from './encode-data.js';
4
4
  export { templateFromAttributes } from './template-from-attributes.js';
5
5
  export { trapFocus, focusFirstFocusableChild } from './focus.js';
6
+ export { downloadAsCSV, downloadAsJSON } from './downloads.js';
package/dist/index.js CHANGED
@@ -3,4 +3,5 @@ export { parseHTML, parseHTMLElement } from './parse-html.js';
3
3
  export { EncodedData, decodeData } from './encode-data.js';
4
4
  export { templateFromAttributes } from './template-from-attributes.js';
5
5
  export { trapFocus, focusFirstFocusableChild } from './focus.js';
6
+ export { downloadAsCSV, downloadAsJSON } from './downloads.js';
6
7
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +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;AACvE,OAAO,EAAE,SAAS,EAAE,wBAAwB,EAAE,MAAM,YAAY,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';\nexport { trapFocus, focusFirstFocusableChild } from './focus.js';\n"]}
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;AACvE,OAAO,EAAE,SAAS,EAAE,wBAAwB,EAAE,MAAM,YAAY,CAAC;AACjE,OAAO,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,gBAAgB,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';\nexport { trapFocus, focusFirstFocusableChild } from './focus.js';\nexport { downloadAsCSV, downloadAsJSON } from './downloads.js';\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@prairielearn/browser-utils",
3
- "version": "2.2.14",
3
+ "version": "2.4.0",
4
4
  "type": "module",
5
5
  "repository": {
6
6
  "type": "git",
@@ -14,6 +14,7 @@
14
14
  },
15
15
  "dependencies": {
16
16
  "@prairielearn/html": "^4.0.20",
17
+ "csv-stringify": "^6.6.0",
17
18
  "js-base64": "^3.7.8"
18
19
  },
19
20
  "devDependencies": {
@@ -0,0 +1,44 @@
1
+ import { stringify } from 'csv-stringify/browser/esm/sync';
2
+
3
+ /**
4
+ * Triggers a browser download of a text-based file.
5
+ *
6
+ * @param content The content of the file.
7
+ * @param filename The desired filename.
8
+ * @param mimeType The MIME type of the file.
9
+ */
10
+ export function downloadTextFile(content: string, filename: string, mimeType: string): void {
11
+ if (typeof window === 'undefined') return;
12
+ const blob = new Blob([content], { type: mimeType });
13
+ const url = URL.createObjectURL(blob);
14
+ const a = document.createElement('a');
15
+ a.href = url;
16
+ a.download = filename;
17
+ document.body.append(a);
18
+ a.click();
19
+ a.remove();
20
+ URL.revokeObjectURL(url);
21
+ }
22
+
23
+ /**
24
+ * Triggers a browser download of a JSON file.
25
+ *
26
+ * @param data The data to be included in the JSON file.
27
+ * @param filename The desired filename.
28
+ */
29
+ export function downloadAsJSON(data: any, filename: string): void {
30
+ const jsonContent = JSON.stringify(data, null, 2);
31
+ downloadTextFile(jsonContent, filename, 'application/json');
32
+ }
33
+
34
+ /**
35
+ * Triggers a browser download of a CSV file.
36
+ *
37
+ * @param header The header row of the CSV.
38
+ * @param data The data rows of the CSV.
39
+ * @param filename The desired filename.
40
+ */
41
+ export function downloadAsCSV(header: string[], data: unknown[][], filename: string): void {
42
+ const csvContent = stringify(data, { header: true, columns: header });
43
+ downloadTextFile(csvContent, filename, 'text/csv');
44
+ }
@@ -22,7 +22,7 @@ export function EncodedData<T = unknown>(data: T, elementId: string): HtmlSafeSt
22
22
  * @param elementId The element ID that stores the encoded data, from from EncodedData().
23
23
  * @returns The decoded data.
24
24
  */
25
- export function decodeData<T = any>(elementId: string): T {
25
+ export function decodeData<T = unknown>(elementId: string): T {
26
26
  const base64Data = document.getElementById(elementId)?.textContent;
27
27
  if (base64Data == null) {
28
28
  throw new Error(`No data found in element with ID "${elementId}"`);
package/src/index.ts CHANGED
@@ -3,3 +3,4 @@ export { parseHTML, parseHTMLElement } from './parse-html.js';
3
3
  export { EncodedData, decodeData } from './encode-data.js';
4
4
  export { templateFromAttributes } from './template-from-attributes.js';
5
5
  export { trapFocus, focusFirstFocusableChild } from './focus.js';
6
+ export { downloadAsCSV, downloadAsJSON } from './downloads.js';