@calmlens/sdk 0.16.2 → 0.17.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.
Files changed (112) hide show
  1. package/README.md +399 -162
  2. package/cjs/BrowserUpload-DZ0Vbmxi.js +10 -0
  3. package/cjs/BrowserUpload-uZjVFGfP.d.ts +11 -0
  4. package/cjs/CalmLensAppClient-CV_K26Ce.d.ts +40 -0
  5. package/cjs/CalmLensAppClient-DBGmaDlG.js +89 -0
  6. package/cjs/CalmLensConnection-K1TAD1Z-.js +659 -0
  7. package/cjs/CalmLensServerClient-C-pxiz0C.js +645 -0
  8. package/cjs/CalmLensServerClient-JtP687a3.d.ts +235 -0
  9. package/cjs/CalmLensTypes-9Vt16pc7.d.ts +40400 -0
  10. package/cjs/Classification-94kSYWZw-CMnQKW13.d.ts +5873 -0
  11. package/cjs/CryptoUtils-B_x8lMIx-YBW1-bvL.js +42 -0
  12. package/cjs/FileIcons-Ob003MdS.js +175 -0
  13. package/cjs/Jurisdiction-FusEXb4U-zVs21tDv.js +91 -0
  14. package/cjs/ModerationContext-9lGm-Mna-CcmbRnOy.d.ts +67 -0
  15. package/cjs/ModerationContextLimits-Dj1YAl3G.js +1311 -0
  16. package/cjs/ReactUpload-DxtFMr06.js +54 -0
  17. package/cjs/ReactUpload-nZahpmyg.d.ts +17 -0
  18. package/cjs/TestTriggers-By07LR31-CmK0yTyR.d.ts +271 -0
  19. package/cjs/TestTriggers-DcArt0Nu-CvoaONUB.js +1326 -0
  20. package/cjs/UploadController-BBkN4Ni6.js +618 -0
  21. package/{esm/index-C9Xw1aid.d.ts → cjs/WebhookPayload-2aL31v0J.d.ts} +131 -155
  22. package/cjs/app.d.ts +7 -44
  23. package/cjs/app.js +11 -89
  24. package/cjs/elements.d.ts +88 -0
  25. package/cjs/elements.js +555 -0
  26. package/cjs/file-icons.d.ts +67 -0
  27. package/cjs/file-icons.js +10 -0
  28. package/cjs/index-CI4BzN4W.d.ts +252 -0
  29. package/cjs/index.d.ts +6 -4
  30. package/cjs/index.js +9 -4
  31. package/cjs/react-native.d.ts +56 -0
  32. package/cjs/react-native.js +495 -0
  33. package/cjs/react.d.ts +30 -0
  34. package/cjs/react.js +370 -0
  35. package/cjs/server.d.ts +12 -191
  36. package/cjs/server.js +18 -378
  37. package/cjs/test-inputs.d.ts +2 -2
  38. package/cjs/test-inputs.js +9 -264
  39. package/cjs/testing.d.ts +285 -190
  40. package/cjs/testing.js +2393 -1203
  41. package/cjs/ui-CgZpgpNo.d.ts +526 -0
  42. package/cjs/ui-KceuoVCM.js +583 -0
  43. package/cjs/ui.d.ts +2 -0
  44. package/cjs/ui.js +27 -0
  45. package/cjs/vue.d.ts +490 -0
  46. package/cjs/vue.js +419 -0
  47. package/esm/BrowserUpload-D7-zNmc0.js +5 -0
  48. package/esm/BrowserUpload-uZjVFGfP.d.ts +11 -0
  49. package/esm/CalmLensAppClient-CV_K26Ce.d.ts +40 -0
  50. package/esm/CalmLensAppClient-FVMwrHlH.js +84 -0
  51. package/esm/CalmLensConnection-iyNQHA8L.js +582 -0
  52. package/esm/CalmLensServerClient-JtP687a3.d.ts +235 -0
  53. package/esm/CalmLensServerClient-fQEYKLw4.js +586 -0
  54. package/esm/CalmLensTypes-9Vt16pc7.d.ts +40400 -0
  55. package/esm/Classification-94kSYWZw-CMnQKW13.d.ts +5873 -0
  56. package/esm/CryptoUtils-B_x8lMIx-DhuWGzqn.js +25 -0
  57. package/esm/FileIcons-M6dzjFxd.js +128 -0
  58. package/esm/Jurisdiction-FusEXb4U-1ov6m5yE.js +46 -0
  59. package/esm/ModerationContext-9lGm-Mna-CcmbRnOy.d.ts +67 -0
  60. package/esm/ModerationContextLimits-D1x_WAtP.js +1114 -0
  61. package/esm/ReactUpload-D6yBCkcw.js +37 -0
  62. package/esm/ReactUpload-nZahpmyg.d.ts +17 -0
  63. package/esm/TestTriggers-By07LR31-CmK0yTyR.d.ts +271 -0
  64. package/esm/TestTriggers-DcArt0Nu-s0AQZuWE.js +1266 -0
  65. package/esm/UploadController-txp5NuzH.js +589 -0
  66. package/{cjs/index-C9Xw1aid.d.ts → esm/WebhookPayload-2aL31v0J.d.ts} +131 -155
  67. package/esm/app.d.ts +7 -44
  68. package/esm/app.js +3 -86
  69. package/esm/elements.d.ts +88 -0
  70. package/esm/elements.js +528 -0
  71. package/esm/file-icons.d.ts +67 -0
  72. package/esm/file-icons.js +1 -0
  73. package/esm/index-CI4BzN4W.d.ts +252 -0
  74. package/esm/index.d.ts +6 -4
  75. package/esm/index.js +1 -1
  76. package/esm/react-native.d.ts +56 -0
  77. package/esm/react-native.js +467 -0
  78. package/esm/react.d.ts +30 -0
  79. package/esm/react.js +343 -0
  80. package/esm/server.d.ts +12 -191
  81. package/esm/server.js +3 -369
  82. package/esm/styles.css +147 -0
  83. package/esm/svelte/CalmLensMedia.svelte +134 -0
  84. package/esm/svelte/CalmLensUploader.svelte +175 -0
  85. package/esm/svelte/Icon.svelte +7 -0
  86. package/esm/svelte/index.d.ts +37 -0
  87. package/esm/svelte/index.js +6 -0
  88. package/esm/test-inputs.d.ts +2 -2
  89. package/esm/test-inputs.js +1 -256
  90. package/esm/testing.d.ts +285 -190
  91. package/esm/testing.js +2329 -1122
  92. package/esm/ui-Ca_SpJXf.js +458 -0
  93. package/esm/ui-CgZpgpNo.d.ts +526 -0
  94. package/esm/ui.d.ts +2 -0
  95. package/esm/ui.js +3 -0
  96. package/esm/vue.d.ts +490 -0
  97. package/esm/vue.js +392 -0
  98. package/package.json +110 -11
  99. package/cjs/CalmLensConnection-5Hj3trGr.js +0 -234
  100. package/cjs/CalmLensUtils-BeIl1N6N.js +0 -699
  101. package/cjs/CalmLensVersion-BGpB9t4-.js +0 -480
  102. package/cjs/CalmLensWebhooks-CogUYk8-.js +0 -201
  103. package/cjs/Classification-BNhgwGEm-DmA9VEuE.d.ts +0 -4806
  104. package/cjs/TestTriggers-CRKtulyp-vrzEq2wm.d.ts +0 -44
  105. package/cjs/WebhookPayload-CJoIkGBD.d.ts +0 -11753
  106. package/esm/CalmLensConnection-BAJdoZHy.js +0 -193
  107. package/esm/CalmLensUtils-DqniTKJ8.js +0 -574
  108. package/esm/CalmLensVersion-l_khh4nS.js +0 -415
  109. package/esm/CalmLensWebhooks-BtrKKLvg.js +0 -148
  110. package/esm/Classification-BNhgwGEm-DmA9VEuE.d.ts +0 -4806
  111. package/esm/TestTriggers-CRKtulyp-vrzEq2wm.d.ts +0 -44
  112. package/esm/WebhookPayload-CJoIkGBD.d.ts +0 -11753
@@ -0,0 +1,88 @@
1
+ import { A as CustomUploadResult, B as MediaAsset, C as getDropzoneHint, D as resolveMessages, E as getUploadItemOutcome, G as MediaState, H as MediaOptions, J as resolveMediaProps, K as getMediaState, L as UploadStage, M as UploadController, P as UploadFile, S as formatMessage, T as getUploadItemMessage, U as MediaSettings, V as MediaImageSource, W as MediaSource, _ as UploadItemView, a as createProgressFetch, b as describeUploader, c as IconName, d as UploaderDisplayOptions, g as DEFAULT_MESSAGES, h as CalmLensMessages, i as CALMLENS_STYLES, j as UploadClient, k as CustomUploadContext, l as MediaDisplayOptions, m as getMediaLoadKey, n as CalmLensTheme, o as ICONS, p as getMediaAspectRatio, q as pickMediaSource, r as resolveTheme, s as ICON_ATTRIBUTES, t as CALMLENS_THEME, u as MediaProps, v as UploaderView, w as getMediaUnavailableMessage, x as formatBytes, y as describeUploadItem, z as isFileAccepted } from "./ui-CgZpgpNo.js";
2
+ import { a as UploaderProps, i as UploadSnapshot, n as UploadControllerOptions, r as UploadItem, s as createUploadController, t as CalmLensUpload } from "./BrowserUpload-uZjVFGfP.js";
3
+ /** The `detail` of each event the elements dispatch. */
4
+ export interface CalmLensElementEvents {
5
+ "calmlens-uploaded": CalmLensUpload;
6
+ "calmlens-rejected": UploadItem;
7
+ "calmlens-failed": UploadItem;
8
+ "calmlens-removed": UploadItem;
9
+ "calmlens-change": UploadSnapshot;
10
+ "calmlens-reveal": undefined;
11
+ }
12
+ declare const BaseElement: typeof HTMLElement;
13
+ /**
14
+ * `<calmlens-uploader upload-preset-id="…" token-url="/api/upload-token">` uploads through an
15
+ * upload preset and dispatches `calmlens-uploaded` with `{ assetId, status, file }` for each
16
+ * finished upload. Attributes: `upload-preset-id`, `token-url`, `accept`, `max-file-size`
17
+ * (bytes), `no-auto-upload`, `multiple`, `max-files`, `disabled`, `capture` and `base-url`. Set
18
+ * the `token`, `upload`, `checkStatus`, `messages` and `controller` properties from script for
19
+ * anything an attribute can't hold.
20
+ */
21
+ export declare class CalmLensUploaderElement extends BaseElement {
22
+ #private;
23
+ static observedAttributes: string[];
24
+ /** The upload token, or a function returning one per file, instead of `token-url`. */
25
+ get token(): UploadControllerOptions["token"];
26
+ set token(value: UploadControllerOptions["token"]);
27
+ /** Uploads each file your own way instead of through `upload-preset-id`. */
28
+ get upload(): UploadControllerOptions["upload"];
29
+ set upload(value: UploadControllerOptions["upload"]);
30
+ /** Checks a pending upload made with `upload`. */
31
+ get checkStatus(): UploadControllerOptions["checkStatus"];
32
+ set checkStatus(value: UploadControllerOptions["checkStatus"]);
33
+ /** Creates the client for each upload, for example a `MockCalmLensAppClient` in tests. */
34
+ get createClient(): UploadControllerOptions["createClient"];
35
+ set createClient(value: UploadControllerOptions["createClient"]);
36
+ get messages(): Partial<CalmLensMessages> | undefined;
37
+ set messages(value: Partial<CalmLensMessages> | undefined);
38
+ /**
39
+ * The upload queue, for reading items or calling `start`, `retry`, `remove` and `clear`. Set it
40
+ * to show a queue your script created; its own options then apply instead of the attributes.
41
+ */
42
+ get controller(): UploadController<File>;
43
+ set controller(value: UploadController<File>);
44
+ connectedCallback(): void;
45
+ disconnectedCallback(): void;
46
+ attributeChangedCallback(): void;
47
+ }
48
+ /**
49
+ * `<calmlens-media project-id="…" asset-id="…" status="allowed" alt="…">` shows an asset from the
50
+ * CalmLens CDN with a blurred placeholder while it loads. Age-restricted assets show the
51
+ * placeholder and a reveal button, which dispatches `calmlens-reveal`, until `age-confirmed` is set.
52
+ * Attributes: `project-id`, `asset-id`, `status`, `age-confirmed`, `type`, `resource`, `lens`,
53
+ * `transform`, `widths` (comma-separated), `url`, `no-placeholder`, `placeholder-hash`,
54
+ * `placeholder-url`, `average-color`, `cdn-base-url`, `alt`, `aspect-ratio`, `width`, `height`,
55
+ * `sizes`, `fit`, `position`, `eager`, `fetch-priority` and `no-controls`. Set the
56
+ * `asset` property to an API asset instead of `project-id` and `asset-id`.
57
+ */
58
+ export declare class CalmLensMediaElement extends BaseElement {
59
+ #private;
60
+ static observedAttributes: string[];
61
+ get messages(): Partial<CalmLensMessages> | undefined;
62
+ set messages(value: Partial<CalmLensMessages> | undefined);
63
+ /**
64
+ * An asset from the API, in place of `project-id` and `asset-id`. It supplies the status, type
65
+ * and preview too; attributes you set win over it.
66
+ */
67
+ get asset(): MediaAsset | undefined;
68
+ set asset(value: MediaAsset | undefined);
69
+ connectedCallback(): void;
70
+ attributeChangedCallback(): void;
71
+ }
72
+ /** Defines `<calmlens-uploader>` and `<calmlens-media>`, if they aren't defined yet. */
73
+ export declare const defineCalmLensElements: () => void;
74
+ declare global {
75
+ interface HTMLElementTagNameMap {
76
+ "calmlens-uploader": CalmLensUploaderElement;
77
+ "calmlens-media": CalmLensMediaElement;
78
+ }
79
+ interface HTMLElementEventMap {
80
+ "calmlens-uploaded": CustomEvent<CalmLensUpload>;
81
+ "calmlens-rejected": CustomEvent<UploadItem>;
82
+ "calmlens-failed": CustomEvent<UploadItem>;
83
+ "calmlens-removed": CustomEvent<UploadItem>;
84
+ "calmlens-change": CustomEvent<UploadSnapshot>;
85
+ "calmlens-reveal": CustomEvent<undefined>;
86
+ }
87
+ }
88
+ export { CALMLENS_STYLES, CALMLENS_THEME, type CalmLensMessages, type CalmLensTheme, type CalmLensUpload, type CustomUploadContext, type CustomUploadResult, DEFAULT_MESSAGES, ICONS, ICON_ATTRIBUTES, type IconName, type MediaAsset, type MediaDisplayOptions, type MediaImageSource, type MediaOptions, type MediaProps, type MediaSettings, type MediaSource, type MediaState, type UploadClient, UploadController, type UploadControllerOptions, type UploadFile, type UploadItem, type UploadItemView, type UploadSnapshot, type UploadStage, type UploaderDisplayOptions, type UploaderProps, type UploaderView, createProgressFetch, createUploadController, describeUploadItem, describeUploader, formatBytes, formatMessage, getDropzoneHint, getMediaAspectRatio, getMediaLoadKey, getMediaState, getMediaUnavailableMessage, getUploadItemMessage, getUploadItemOutcome, isFileAccepted, pickMediaSource, resolveMediaProps, resolveMessages, resolveTheme };
@@ -0,0 +1,528 @@
1
+ import { _ as getMediaState, a as getMediaAspectRatio, b as ICONS, c as describeUploadItem, d as formatMessage, f as getDropzoneHint, g as resolveMessages, h as getUploadItemOutcome, i as resolveTheme, l as describeUploader, m as getUploadItemMessage, o as getMediaLoadKey, p as getMediaUnavailableMessage, r as CALMLENS_THEME, s as DEFAULT_MESSAGES, t as CALMLENS_STYLES, u as formatBytes, v as pickMediaSource, x as ICON_ATTRIBUTES, y as resolveMediaProps } from "./ui-Ca_SpJXf.js";
2
+ import { i as createProgressFetch, n as createUploadController$1, r as isFileAccepted, t as UploadController } from "./UploadController-txp5NuzH.js";
3
+ import { t as createUploadController } from "./BrowserUpload-D7-zNmc0.js";
4
+ // src/elements.ts
5
+ const el = (tag, attributes = {}, children = []) => {
6
+ const node = document.createElement(tag);
7
+ for (const [name, value] of Object.entries(attributes)) {
8
+ if (value === void 0 || value === false) continue;
9
+ node.setAttribute(name, value === true ? "" : String(value));
10
+ }
11
+ for (const child of children) if (child) node.append(child);
12
+ return node;
13
+ };
14
+ const icon = (name) => {
15
+ const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
16
+ for (const [attribute, value] of Object.entries(ICON_ATTRIBUTES)) svg.setAttribute(attribute, value);
17
+ const path = document.createElementNS("http://www.w3.org/2000/svg", "path");
18
+ path.setAttribute("d", ICONS[name]);
19
+ svg.append(path);
20
+ return svg;
21
+ };
22
+ /** A row's file-type icon, from the SDK's own SVG markup. */
23
+ const fileThumbnail = (svg) => {
24
+ const thumbnail = el("span", {
25
+ class: "calmlens-upload__thumb",
26
+ "data-kind": "file",
27
+ "aria-hidden": "true"
28
+ });
29
+ thumbnail.innerHTML = svg;
30
+ return thumbnail;
31
+ };
32
+ const numberAttribute = (element, name) => {
33
+ const value = element.getAttribute(name);
34
+ return value === null || value === "" ? void 0 : Number(value);
35
+ };
36
+ /**
37
+ * Moves properties set on an element before it was defined onto its setters. Until then they're
38
+ * plain own properties, which would hide the class's accessors.
39
+ */
40
+ const upgradeProperties = (element, names) => {
41
+ const record = element;
42
+ for (const name of names) {
43
+ if (!Object.hasOwn(element, name)) continue;
44
+ const value = record[name];
45
+ delete record[name];
46
+ record[name] = value;
47
+ }
48
+ };
49
+ const BaseElement = typeof HTMLElement === "undefined" ? class {} : HTMLElement;
50
+ /**
51
+ * `<calmlens-uploader upload-preset-id="…" token-url="/api/upload-token">` uploads through an
52
+ * upload preset and dispatches `calmlens-uploaded` with `{ assetId, status, file }` for each
53
+ * finished upload. Attributes: `upload-preset-id`, `token-url`, `accept`, `max-file-size`
54
+ * (bytes), `no-auto-upload`, `multiple`, `max-files`, `disabled`, `capture` and `base-url`. Set
55
+ * the `token`, `upload`, `checkStatus`, `messages` and `controller` properties from script for
56
+ * anything an attribute can't hold.
57
+ */
58
+ var CalmLensUploaderElement = class extends BaseElement {
59
+ static {
60
+ this.observedAttributes = [
61
+ "upload-preset-id",
62
+ "token-url",
63
+ "accept",
64
+ "max-file-size",
65
+ "no-auto-upload",
66
+ "multiple",
67
+ "max-files",
68
+ "disabled",
69
+ "capture",
70
+ "base-url"
71
+ ];
72
+ }
73
+ #token;
74
+ #upload;
75
+ #checkStatus;
76
+ #createClient;
77
+ #messages;
78
+ #controller;
79
+ /** Whether `controller` was set from script, so its options and uploads aren't this element's. */
80
+ #external = false;
81
+ #unsubscribe;
82
+ #dragging = false;
83
+ #input;
84
+ /** The upload token, or a function returning one per file, instead of `token-url`. */
85
+ get token() {
86
+ return this.#token;
87
+ }
88
+ set token(value) {
89
+ this.#token = value;
90
+ this.#updateOptions();
91
+ }
92
+ /** Uploads each file your own way instead of through `upload-preset-id`. */
93
+ get upload() {
94
+ return this.#upload;
95
+ }
96
+ set upload(value) {
97
+ this.#upload = value;
98
+ this.#updateOptions();
99
+ }
100
+ /** Checks a pending upload made with `upload`. */
101
+ get checkStatus() {
102
+ return this.#checkStatus;
103
+ }
104
+ set checkStatus(value) {
105
+ this.#checkStatus = value;
106
+ this.#updateOptions();
107
+ }
108
+ /** Creates the client for each upload, for example a `MockCalmLensAppClient` in tests. */
109
+ get createClient() {
110
+ return this.#createClient;
111
+ }
112
+ set createClient(value) {
113
+ this.#createClient = value;
114
+ this.#updateOptions();
115
+ }
116
+ get messages() {
117
+ return this.#messages;
118
+ }
119
+ set messages(value) {
120
+ this.#messages = value;
121
+ this.#render();
122
+ }
123
+ /**
124
+ * The upload queue, for reading items or calling `start`, `retry`, `remove` and `clear`. Set it
125
+ * to show a queue your script created; its own options then apply instead of the attributes.
126
+ */
127
+ get controller() {
128
+ this.#controller ??= createUploadController$1(this.#options());
129
+ return this.#controller;
130
+ }
131
+ set controller(value) {
132
+ if (value === this.#controller) return;
133
+ this.#unsubscribe?.();
134
+ if (!this.#external) this.#controller?.clear();
135
+ this.#controller = value;
136
+ this.#external = true;
137
+ if (this.isConnected) {
138
+ this.#subscribe();
139
+ this.#render();
140
+ }
141
+ }
142
+ #updateOptions() {
143
+ if (!this.#external) this.#controller?.setOptions(this.#options());
144
+ }
145
+ #subscribe() {
146
+ this.#unsubscribe = this.controller.subscribe((snapshot) => {
147
+ this.#renderItems();
148
+ this.dispatchEvent(new CustomEvent("calmlens-change", { detail: snapshot }));
149
+ });
150
+ }
151
+ #options() {
152
+ const emit = (type, detail) => this.dispatchEvent(new CustomEvent(type, {
153
+ detail,
154
+ bubbles: true
155
+ }));
156
+ return {
157
+ uploadPresetId: this.getAttribute("upload-preset-id") ?? void 0,
158
+ upload: this.upload,
159
+ checkStatus: this.checkStatus,
160
+ createClient: this.createClient,
161
+ token: this.token,
162
+ tokenUrl: this.getAttribute("token-url") ?? void 0,
163
+ accept: this.getAttribute("accept") ?? void 0,
164
+ maxFileSize: numberAttribute(this, "max-file-size"),
165
+ autoUpload: !this.hasAttribute("no-auto-upload"),
166
+ multiple: this.hasAttribute("multiple"),
167
+ maxFiles: numberAttribute(this, "max-files"),
168
+ baseUrl: this.getAttribute("base-url") ?? void 0,
169
+ onUploaded: (upload) => emit("calmlens-uploaded", upload),
170
+ onRejected: (item) => emit("calmlens-rejected", item),
171
+ onFailed: (item) => emit("calmlens-failed", item),
172
+ onRemoved: (item) => emit("calmlens-removed", item)
173
+ };
174
+ }
175
+ connectedCallback() {
176
+ upgradeProperties(this, [
177
+ "token",
178
+ "upload",
179
+ "checkStatus",
180
+ "createClient",
181
+ "messages",
182
+ "controller"
183
+ ]);
184
+ this.classList.add("calmlens-uploader");
185
+ this.#subscribe();
186
+ this.#render();
187
+ }
188
+ disconnectedCallback() {
189
+ this.#unsubscribe?.();
190
+ if (!this.#external) this.controller.clear();
191
+ }
192
+ attributeChangedCallback() {
193
+ this.#updateOptions();
194
+ if (this.isConnected) this.#render();
195
+ }
196
+ #render() {
197
+ if (!this.isConnected) return;
198
+ this.#updateOptions();
199
+ const { accept, multiple } = this.controller.options;
200
+ const messages = resolveMessages(this.#messages);
201
+ const disabled = this.hasAttribute("disabled");
202
+ const view = describeUploader(this.controller.getSnapshot(), this.controller.options, messages);
203
+ const input = el("input", {
204
+ type: "file",
205
+ hidden: true,
206
+ accept,
207
+ multiple,
208
+ capture: this.getAttribute("capture") ?? void 0,
209
+ disabled
210
+ });
211
+ this.#input = input;
212
+ input.addEventListener("change", () => {
213
+ if (input.files) this.controller.addFiles(input.files);
214
+ input.value = "";
215
+ });
216
+ const button = el("button", {
217
+ type: "button",
218
+ class: "calmlens-button",
219
+ "data-variant": "outline",
220
+ disabled
221
+ }, [view.chooseLabel]);
222
+ button.addEventListener("click", (event) => {
223
+ event.stopPropagation();
224
+ this.#openPicker();
225
+ });
226
+ const title = el("p", { class: "calmlens-dropzone__title" }, [messages.dropzoneTitle]);
227
+ const zone = el("div", {
228
+ class: "calmlens-dropzone",
229
+ "data-disabled": disabled
230
+ }, [
231
+ el("span", { class: "calmlens-dropzone__icon" }, [icon("upload")]),
232
+ el("div", { class: "calmlens-dropzone__text" }, [title, view.hint && el("p", { class: "calmlens-dropzone__hint" }, [view.hint])]),
233
+ button
234
+ ]);
235
+ const setDragging = (dragging) => {
236
+ this.#dragging = dragging;
237
+ zone.toggleAttribute("data-dragging", dragging);
238
+ title.textContent = dragging ? messages.dropzoneTitleDragging : messages.dropzoneTitle;
239
+ };
240
+ const onDragOver = (event) => {
241
+ event.preventDefault();
242
+ if (!this.hasAttribute("disabled") && !this.#dragging) setDragging(true);
243
+ };
244
+ zone.addEventListener("click", () => this.#openPicker());
245
+ zone.addEventListener("dragover", onDragOver);
246
+ zone.addEventListener("dragenter", onDragOver);
247
+ zone.addEventListener("dragleave", (event) => {
248
+ if (!zone.contains(event.relatedTarget)) setDragging(false);
249
+ });
250
+ zone.addEventListener("drop", (event) => {
251
+ event.preventDefault();
252
+ setDragging(false);
253
+ if (!this.hasAttribute("disabled") && event.dataTransfer) this.controller.addFiles(event.dataTransfer.files);
254
+ });
255
+ zone.addEventListener("paste", (event) => {
256
+ const files = event.clipboardData?.files;
257
+ if (!this.hasAttribute("disabled") && files?.length) this.controller.addFiles(files);
258
+ });
259
+ this.replaceChildren(zone, input, el("ul", {
260
+ class: "calmlens-uploads",
261
+ hidden: true
262
+ }));
263
+ this.#renderItems();
264
+ }
265
+ #openPicker() {
266
+ if (!this.hasAttribute("disabled")) this.#input?.click();
267
+ }
268
+ #renderItems() {
269
+ const list = this.querySelector(":scope > .calmlens-uploads");
270
+ const zone = this.querySelector(":scope > .calmlens-dropzone");
271
+ if (!list || !zone) return;
272
+ const messages = resolveMessages(this.#messages);
273
+ const snapshot = this.controller.getSnapshot();
274
+ const { items } = snapshot;
275
+ const view = describeUploader(snapshot, this.controller.options, messages);
276
+ zone.hidden = !view.showDropzone;
277
+ list.hidden = items.length === 0;
278
+ const thumbnails = new Map([...list.querySelectorAll(".calmlens-upload")].map((row) => [row.dataset.key, row.querySelector(".calmlens-upload__thumb")]));
279
+ const disabled = this.hasAttribute("disabled");
280
+ list.replaceChildren(...items.map((item) => {
281
+ const itemView = describeUploadItem(item, messages);
282
+ const thumbnail = thumbnails.get(item.key) ?? (item.previewUrl ? el("img", {
283
+ class: "calmlens-upload__thumb",
284
+ src: item.previewUrl,
285
+ alt: ""
286
+ }) : fileThumbnail(itemView.fileIcon));
287
+ const bar = el("div", { class: "calmlens-progress__bar" });
288
+ if (itemView.progressPercent !== void 0) bar.style.width = `${itemView.progressPercent}%`;
289
+ const chooseAnother = !view.showDropzone && el("button", {
290
+ type: "button",
291
+ class: "calmlens-button",
292
+ "data-variant": "outline",
293
+ "data-size": "small",
294
+ disabled
295
+ }, [messages.chooseAnother]);
296
+ if (chooseAnother) chooseAnother.addEventListener("click", () => this.#openPicker());
297
+ const retry = itemView.canRetry && el("button", {
298
+ type: "button",
299
+ class: "calmlens-icon-button",
300
+ "aria-label": messages.retry,
301
+ title: messages.retry
302
+ }, [icon("retry")]);
303
+ if (retry) retry.addEventListener("click", () => this.controller.retry(item.key));
304
+ const remove = el("button", {
305
+ type: "button",
306
+ class: "calmlens-icon-button",
307
+ "aria-label": itemView.removeLabel,
308
+ title: itemView.removeLabel
309
+ }, [icon("remove")]);
310
+ remove.addEventListener("click", () => this.controller.remove(item.key));
311
+ const row = el("li", {
312
+ class: "calmlens-upload",
313
+ "data-stage": item.stage,
314
+ "data-outcome": itemView.outcome
315
+ }, [
316
+ thumbnail,
317
+ el("div", { class: "calmlens-upload__body" }, [
318
+ el("span", { class: "calmlens-upload__name" }, [item.file.name]),
319
+ el("span", { class: "calmlens-upload__size" }, [itemView.size]),
320
+ itemView.busy && el("div", {
321
+ class: "calmlens-progress",
322
+ role: "progressbar",
323
+ "aria-label": itemView.message,
324
+ "aria-valuemin": 0,
325
+ "aria-valuemax": 100,
326
+ "aria-valuenow": itemView.progressPercent
327
+ }, [bar]),
328
+ itemView.message && el("p", {
329
+ class: "calmlens-upload__message",
330
+ role: "status"
331
+ }, [itemView.message])
332
+ ]),
333
+ el("div", { class: "calmlens-upload__actions" }, [
334
+ chooseAnother,
335
+ retry,
336
+ remove
337
+ ])
338
+ ]);
339
+ row.dataset.key = item.key;
340
+ return row;
341
+ }));
342
+ }
343
+ };
344
+ /**
345
+ * `<calmlens-media project-id="…" asset-id="…" status="allowed" alt="…">` shows an asset from the
346
+ * CalmLens CDN with a blurred placeholder while it loads. Age-restricted assets show the
347
+ * placeholder and a reveal button, which dispatches `calmlens-reveal`, until `age-confirmed` is set.
348
+ * Attributes: `project-id`, `asset-id`, `status`, `age-confirmed`, `type`, `resource`, `lens`,
349
+ * `transform`, `widths` (comma-separated), `url`, `no-placeholder`, `placeholder-hash`,
350
+ * `placeholder-url`, `average-color`, `cdn-base-url`, `alt`, `aspect-ratio`, `width`, `height`,
351
+ * `sizes`, `fit`, `position`, `eager`, `fetch-priority` and `no-controls`. Set the
352
+ * `asset` property to an API asset instead of `project-id` and `asset-id`.
353
+ */
354
+ var CalmLensMediaElement = class extends BaseElement {
355
+ static {
356
+ this.observedAttributes = [
357
+ "project-id",
358
+ "asset-id",
359
+ "status",
360
+ "content-removed",
361
+ "type",
362
+ "resource",
363
+ "lens",
364
+ "transform",
365
+ "widths",
366
+ "url",
367
+ "no-placeholder",
368
+ "placeholder-hash",
369
+ "placeholder-url",
370
+ "average-color",
371
+ "cdn-base-url",
372
+ "viewer-claims",
373
+ "alt",
374
+ "aspect-ratio",
375
+ "width",
376
+ "height",
377
+ "sizes",
378
+ "fit",
379
+ "position",
380
+ "eager",
381
+ "fetch-priority",
382
+ "no-controls"
383
+ ];
384
+ }
385
+ #messages;
386
+ #asset;
387
+ #loadKey;
388
+ get messages() {
389
+ return this.#messages;
390
+ }
391
+ set messages(value) {
392
+ this.#messages = value;
393
+ this.#loadKey = void 0;
394
+ this.#render();
395
+ }
396
+ /**
397
+ * An asset from the API, in place of `project-id` and `asset-id`. It supplies the status, type
398
+ * and preview too; attributes you set win over it.
399
+ */
400
+ get asset() {
401
+ return this.#asset;
402
+ }
403
+ set asset(value) {
404
+ this.#asset = value;
405
+ this.#render();
406
+ }
407
+ connectedCallback() {
408
+ upgradeProperties(this, ["messages", "asset"]);
409
+ this.classList.add("calmlens-media");
410
+ this.setAttribute("role", "figure");
411
+ this.#render();
412
+ }
413
+ attributeChangedCallback() {
414
+ this.#render();
415
+ }
416
+ #mediaOptions() {
417
+ const list = (name) => this.getAttribute(name)?.split(",").map((value) => value.trim()).filter(Boolean);
418
+ return resolveMediaProps({
419
+ asset: this.#asset,
420
+ projectId: this.getAttribute("project-id") ?? void 0,
421
+ assetId: this.getAttribute("asset-id") ?? void 0,
422
+ status: this.getAttribute("status") ?? void 0,
423
+ contentRemoved: this.hasAttribute("content-removed") ? true : void 0,
424
+ type: this.getAttribute("type") ?? void 0,
425
+ resource: this.getAttribute("resource") ?? void 0,
426
+ lens: this.getAttribute("lens") ?? void 0,
427
+ transform: this.getAttribute("transform") ?? void 0,
428
+ widths: list("widths")?.map(Number),
429
+ url: this.getAttribute("url") ?? void 0,
430
+ placeholder: !this.hasAttribute("no-placeholder"),
431
+ placeholderHash: this.getAttribute("placeholder-hash") ?? void 0,
432
+ placeholderUrl: this.getAttribute("placeholder-url") ?? void 0,
433
+ viewerClaims: this.hasAttribute("viewer-claims") ? JSON.parse(this.getAttribute("viewer-claims")) : void 0,
434
+ cdnBaseUrl: this.getAttribute("cdn-base-url") ?? void 0,
435
+ width: numberAttribute(this, "width"),
436
+ height: numberAttribute(this, "height"),
437
+ averageColor: this.getAttribute("average-color") ?? void 0
438
+ });
439
+ }
440
+ #render() {
441
+ if (!this.isConnected) return;
442
+ const options = this.#mediaOptions();
443
+ if (!options.projectId || !options.assetId) return;
444
+ const state = getMediaState(options);
445
+ const loadKey = `${getMediaLoadKey(state)} ${this.getAttribute("alt")} ${this.getAttribute("sizes")} ${options.viewerClaims?.ageConfirmed}`;
446
+ const ratio = getMediaAspectRatio({
447
+ aspectRatio: numberAttribute(this, "aspect-ratio"),
448
+ width: options.width,
449
+ height: options.height
450
+ });
451
+ this.style.aspectRatio = ratio ? String(ratio) : "";
452
+ this.toggleAttribute("data-sized", Boolean(ratio));
453
+ this.style.backgroundColor = options.averageColor ?? "";
454
+ this.style.setProperty("--calmlens-fit", this.getAttribute("fit"));
455
+ this.style.setProperty("--calmlens-position", this.getAttribute("position"));
456
+ this.dataset.kind = state.kind;
457
+ if (loadKey === this.#loadKey) return;
458
+ this.#loadKey = loadKey;
459
+ this.dataset.state = "loading";
460
+ const messages = resolveMessages(this.#messages);
461
+ const alt = this.getAttribute("alt") ?? "";
462
+ const sizes = this.getAttribute("sizes") ?? void 0;
463
+ const placeholder = state.kind !== "audio" && state.kind !== "unavailable" && state.placeholderUrl ? el("img", {
464
+ class: "calmlens-media__placeholder",
465
+ src: state.placeholderUrl,
466
+ alt: "",
467
+ "aria-hidden": "true"
468
+ }) : void 0;
469
+ const notice = (children) => el("div", { class: "calmlens-media__notice" }, children);
470
+ const unavailable = () => notice([el("p", {}, [icon("unavailable"), el("span", { class: "calmlens-media__label" }, [getMediaUnavailableMessage(state, messages)])])]);
471
+ const fail = () => {
472
+ this.dataset.state = "error";
473
+ this.replaceChildren(unavailable());
474
+ };
475
+ const controls = !this.hasAttribute("no-controls");
476
+ if (state.kind === "unavailable") this.replaceChildren(unavailable());
477
+ else if (state.kind === "gated") {
478
+ const reveal = el("button", {
479
+ type: "button",
480
+ class: "calmlens-button"
481
+ }, [messages.reveal]);
482
+ reveal.addEventListener("click", () => this.dispatchEvent(new CustomEvent("calmlens-reveal", { bubbles: true })));
483
+ this.replaceChildren(...[placeholder].filter((node) => node !== void 0), notice([
484
+ el("strong", {}, [messages.sensitiveTitle]),
485
+ el("p", {}, [messages.sensitiveBody]),
486
+ reveal
487
+ ]));
488
+ } else if (state.kind === "image") {
489
+ const image = el("img", {
490
+ class: "calmlens-media__content",
491
+ src: state.src,
492
+ srcset: state.srcSet,
493
+ sizes,
494
+ alt,
495
+ width: options.width,
496
+ height: options.height,
497
+ loading: this.hasAttribute("eager") ? void 0 : "lazy",
498
+ decoding: "async",
499
+ fetchpriority: this.getAttribute("fetch-priority") ?? void 0
500
+ });
501
+ image.addEventListener("load", () => {
502
+ this.dataset.state = "loaded";
503
+ placeholder?.remove();
504
+ });
505
+ image.addEventListener("error", fail);
506
+ this.replaceChildren(...[placeholder, image].filter((node) => node !== void 0));
507
+ } else {
508
+ const media = el(state.kind, {
509
+ class: "calmlens-media__content",
510
+ src: state.src,
511
+ controls,
512
+ playsinline: state.kind === "video",
513
+ preload: "metadata",
514
+ "aria-label": alt || void 0
515
+ });
516
+ media.addEventListener("error", fail);
517
+ this.replaceChildren(media);
518
+ }
519
+ }
520
+ };
521
+ /** Defines `<calmlens-uploader>` and `<calmlens-media>`, if they aren't defined yet. */
522
+ const defineCalmLensElements = () => {
523
+ if (typeof customElements === "undefined") return;
524
+ if (!customElements.get("calmlens-uploader")) customElements.define("calmlens-uploader", CalmLensUploaderElement);
525
+ if (!customElements.get("calmlens-media")) customElements.define("calmlens-media", CalmLensMediaElement);
526
+ };
527
+ defineCalmLensElements();
528
+ export { CALMLENS_STYLES, CALMLENS_THEME, CalmLensMediaElement, CalmLensUploaderElement, DEFAULT_MESSAGES, ICONS, ICON_ATTRIBUTES, UploadController, createProgressFetch, createUploadController, defineCalmLensElements, describeUploadItem, describeUploader, formatBytes, formatMessage, getDropzoneHint, getMediaAspectRatio, getMediaLoadKey, getMediaState, getMediaUnavailableMessage, getUploadItemMessage, getUploadItemOutcome, isFileAccepted, pickMediaSource, resolveMediaProps, resolveMessages, resolveTheme };
@@ -0,0 +1,67 @@
1
+ /**
2
+ * File-type icons for files without an image preview: a file outline in the family's colour with
3
+ * the family's symbol and an extension label, drawn the same way by every framework. The symbols are
4
+ * Tabler icons (MIT) as 24×24 stroke paths.
5
+ */
6
+ type FileIconFamily = "documents" | "spreadsheets" | "presentations" | "images" | "audio" | "video" | "archives" | "code" | "data" | "design" | "applications" | "fonts" | "maps" | "games" | "system" | "security" | "unknown";
7
+ /** The families `getFileIcon` finds from a file's name and MIME type. */
8
+ type UploadFileIconFamily = Exclude<FileIconFamily, "applications" | "maps" | "games" | "system" | "security" | "unknown">;
9
+ /** The file outline every family's icon is drawn on. */
10
+ export declare const FILE_ICON_OUTLINE = "M14 3v4a1 1 0 0 0 1 1h4 M17 21h-10a2 2 0 0 1 -2 -2v-14a2 2 0 0 1 2 -2h7l5 5v11a2 2 0 0 1 -2 2";
11
+ /** The symbols of the families `getFileIcon` finds, which the upload widget draws. */
12
+ export declare const FILE_ICON_SYMBOLS: Record<UploadFileIconFamily, string>;
13
+ /**
14
+ * The symbols of the families only an app's own file detection finds, kept apart so the upload
15
+ * widget doesn't bundle them.
16
+ */
17
+ export declare const MORE_FILE_ICON_SYMBOLS: Record<Exclude<FileIconFamily, UploadFileIconFamily | "unknown">, string>;
18
+ export declare const FILE_ICON_COLORS: Record<FileIconFamily, string>;
19
+ /** A file's icon family and the extension its label shows, such as `pdf`. */
20
+ export declare const getFileIcon: (file: {
21
+ name: string;
22
+ type: string;
23
+ }) => {
24
+ family: UploadFileIconFamily;
25
+ extension: string;
26
+ };
27
+ /**
28
+ * Where each part of the icon goes in its 24×24 box: the symbol inside the outline, above a band
29
+ * holding the extension. Icons 32 pixels or smaller leave the band out and centre the symbol.
30
+ */
31
+ export declare const getFileIconLayout: (size: number) => {
32
+ compact: boolean;
33
+ symbol: {
34
+ x: number;
35
+ y: number;
36
+ width: number;
37
+ height: number;
38
+ };
39
+ outlineStroke: number;
40
+ /** The band and its label, in a half-scale group: `transform="scale(0.5)"`. */
41
+ band: {
42
+ x: number;
43
+ y: number;
44
+ width: number;
45
+ height: number;
46
+ rx: number;
47
+ };
48
+ label: {
49
+ x: number;
50
+ y: number;
51
+ };
52
+ };
53
+ /** The label's font size, and a squeezed width for long extensions. */
54
+ export declare const getFileIconLabel: (extension: string) => {
55
+ text: string;
56
+ fontSize: number;
57
+ textLength: number | undefined;
58
+ };
59
+ /**
60
+ * The icon as SVG markup, for the components to insert as it is. `extension` must come from
61
+ * `getFileIcon`, which keeps only letters and digits.
62
+ */
63
+ export declare const renderFileIconSvg: ({ family, extension }: {
64
+ family: UploadFileIconFamily | "unknown";
65
+ extension: string;
66
+ }, size: number) => string;
67
+ export type { FileIconFamily, UploadFileIconFamily };
@@ -0,0 +1 @@
1
+ export { t as FILE_ICON_COLORS, n as FILE_ICON_OUTLINE, r as FILE_ICON_SYMBOLS, i as MORE_FILE_ICON_SYMBOLS, a as getFileIcon, o as getFileIconLabel, s as getFileIconLayout, c as renderFileIconSvg } from "./FileIcons-M6dzjFxd.js";