@h-k-dev/angular-email-editor 0.0.0-stage → 0.0.2

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 (51) hide show
  1. package/README.md +127 -2
  2. package/fesm2022/h-k-dev-angular-email-editor-actions.mjs +179 -0
  3. package/fesm2022/h-k-dev-angular-email-editor-actions.mjs.map +1 -0
  4. package/fesm2022/h-k-dev-angular-email-editor-address-chip.mjs +383 -0
  5. package/fesm2022/h-k-dev-angular-email-editor-address-chip.mjs.map +1 -0
  6. package/fesm2022/h-k-dev-angular-email-editor-address-input.mjs +1106 -0
  7. package/fesm2022/h-k-dev-angular-email-editor-address-input.mjs.map +1 -0
  8. package/fesm2022/h-k-dev-angular-email-editor-address.mjs +489 -0
  9. package/fesm2022/h-k-dev-angular-email-editor-address.mjs.map +1 -0
  10. package/fesm2022/h-k-dev-angular-email-editor-anchor.mjs +64 -0
  11. package/fesm2022/h-k-dev-angular-email-editor-anchor.mjs.map +1 -0
  12. package/fesm2022/h-k-dev-angular-email-editor-attachment-chip.mjs +387 -0
  13. package/fesm2022/h-k-dev-angular-email-editor-attachment-chip.mjs.map +1 -0
  14. package/fesm2022/h-k-dev-angular-email-editor-attachment-chips.mjs +80 -0
  15. package/fesm2022/h-k-dev-angular-email-editor-attachment-chips.mjs.map +1 -0
  16. package/fesm2022/h-k-dev-angular-email-editor-chat-input.mjs +346 -0
  17. package/fesm2022/h-k-dev-angular-email-editor-chat-input.mjs.map +1 -0
  18. package/fesm2022/h-k-dev-angular-email-editor-focus.mjs +78 -0
  19. package/fesm2022/h-k-dev-angular-email-editor-focus.mjs.map +1 -0
  20. package/fesm2022/h-k-dev-angular-email-editor-image.mjs +132 -0
  21. package/fesm2022/h-k-dev-angular-email-editor-image.mjs.map +1 -0
  22. package/fesm2022/h-k-dev-angular-email-editor-material.mjs +124 -0
  23. package/fesm2022/h-k-dev-angular-email-editor-material.mjs.map +1 -0
  24. package/fesm2022/h-k-dev-angular-email-editor-metrics.mjs +243 -0
  25. package/fesm2022/h-k-dev-angular-email-editor-metrics.mjs.map +1 -0
  26. package/fesm2022/h-k-dev-angular-email-editor-palette.mjs +87 -0
  27. package/fesm2022/h-k-dev-angular-email-editor-palette.mjs.map +1 -0
  28. package/fesm2022/h-k-dev-angular-email-editor-proposal.mjs +307 -0
  29. package/fesm2022/h-k-dev-angular-email-editor-proposal.mjs.map +1 -0
  30. package/fesm2022/h-k-dev-angular-email-editor-suggestion-menu.mjs +392 -0
  31. package/fesm2022/h-k-dev-angular-email-editor-suggestion-menu.mjs.map +1 -0
  32. package/fesm2022/h-k-dev-angular-email-editor.mjs +18820 -0
  33. package/fesm2022/h-k-dev-angular-email-editor.mjs.map +1 -0
  34. package/package.json +133 -3
  35. package/styles/_scroll.scss +101 -0
  36. package/types/h-k-dev-angular-email-editor-actions.d.ts +147 -0
  37. package/types/h-k-dev-angular-email-editor-address-chip.d.ts +234 -0
  38. package/types/h-k-dev-angular-email-editor-address-input.d.ts +449 -0
  39. package/types/h-k-dev-angular-email-editor-address.d.ts +125 -0
  40. package/types/h-k-dev-angular-email-editor-anchor.d.ts +44 -0
  41. package/types/h-k-dev-angular-email-editor-attachment-chip.d.ts +271 -0
  42. package/types/h-k-dev-angular-email-editor-attachment-chips.d.ts +48 -0
  43. package/types/h-k-dev-angular-email-editor-chat-input.d.ts +101 -0
  44. package/types/h-k-dev-angular-email-editor-focus.d.ts +46 -0
  45. package/types/h-k-dev-angular-email-editor-image.d.ts +66 -0
  46. package/types/h-k-dev-angular-email-editor-material.d.ts +65 -0
  47. package/types/h-k-dev-angular-email-editor-metrics.d.ts +110 -0
  48. package/types/h-k-dev-angular-email-editor-palette.d.ts +51 -0
  49. package/types/h-k-dev-angular-email-editor-proposal.d.ts +170 -0
  50. package/types/h-k-dev-angular-email-editor-suggestion-menu.d.ts +177 -0
  51. package/types/h-k-dev-angular-email-editor.d.ts +3959 -0
@@ -0,0 +1,387 @@
1
+ import { isPlatformBrowser, NgTemplateOutlet } from '@angular/common';
2
+ import * as i0 from '@angular/core';
3
+ import { inject, PLATFORM_ID, debounced, computed, TemplateRef, Directive, InjectionToken, LOCALE_ID, DestroyRef, input, booleanAttribute, output, contentChild, signal, Component } from '@angular/core';
4
+
5
+ /** The stages during which the transfer is not done — a waiting one
6
+ included, since the message is no more sendable for it. */
7
+ function attachmentInFlight(status) {
8
+ return (status === 'preprocessing' ||
9
+ status === 'queued' ||
10
+ status === 'uploading' ||
11
+ status === 'postprocessing');
12
+ }
13
+ /** MIME → kind. Ordered: the first match wins, so the specific document
14
+ types are tested before the `text/*` catch-all, and the Office types —
15
+ which all spell out "document" — before the plain document pattern. */
16
+ const KINDS = [
17
+ [/^image\//, 'image'],
18
+ [/^video\//, 'video'],
19
+ [/^audio\//, 'audio'],
20
+ [/pdf$/, 'pdf'],
21
+ [/(zip|compressed|tar|rar|7z)/, 'archive'],
22
+ [/(spreadsheet|excel|csv)/, 'spreadsheet'],
23
+ [/(presentation|powerpoint)/, 'presentation'],
24
+ [/(word|document|rtf|opendocument\.text)/, 'document'],
25
+ [/^message\/|rfc822/, 'message'],
26
+ [/^text\//, 'document'],
27
+ ];
28
+ /** The kind of file a MIME type stands for; `file` when the type is unknown
29
+ or matches nothing above. Case-insensitive — a type off the wire may be
30
+ shouted. */
31
+ function attachmentKind(type) {
32
+ if (!type)
33
+ return 'file';
34
+ const mime = type.toLowerCase();
35
+ for (const [pattern, kind] of KINDS)
36
+ if (pattern.test(mime))
37
+ return kind;
38
+ return 'file';
39
+ }
40
+ const SIZE_UNITS = ['B', 'KB', 'MB', 'GB'];
41
+ /**
42
+ * A byte count the way a mail client prints it: binary steps, one decimal
43
+ * below ten ("2.5 MB", "15 MB"), none for bytes. The number follows the
44
+ * locale ("2,5 MB" in German); the unit labels are the ones every mail
45
+ * client uses, not CLDR's ("kB", "byte").
46
+ */
47
+ function formatAttachmentSize(bytes, locale = 'en-US') {
48
+ let value = Math.max(0, bytes);
49
+ let unit = 0;
50
+ while (value >= 1024 && unit < SIZE_UNITS.length - 1) {
51
+ value /= 1024;
52
+ unit++;
53
+ }
54
+ const digits = unit > 0 && value < 10 ? 1 : 0;
55
+ const number = new Intl.NumberFormat(locale, {
56
+ maximumFractionDigits: digits,
57
+ useGrouping: false,
58
+ }).format(value);
59
+ return `${number} ${SIZE_UNITS[unit]}`;
60
+ }
61
+
62
+ /**
63
+ * The chip's animation state — the part of the motion that CSS alone cannot
64
+ * carry. The stylesheet owns the pictures (the sweep, the fade, the exit,
65
+ * the fill); this file owns the timing they key off, so the two stay in
66
+ * one component and no host has to know a band is moving.
67
+ */
68
+ /** How long the scanning band takes to fade once it is told to go, in ms.
69
+ Matches the stylesheet: its exit animation runs this long, and the host
70
+ attribute that keeps the band on the page holds for the same stretch —
71
+ a tab that never paints must still let go of it. */
72
+ const SCAN_EXIT = 300;
73
+ /**
74
+ * The scanning band's state: `'true'` while `active()`, then `'leaving'`
75
+ * for `exit` ms after it stops, then nothing. The band's CSS keys off both
76
+ * — the sweep runs while the state is set at all, and `leaving` stacks the
77
+ * exit animation on top — so a number that arrives mid-sweep sees the band
78
+ * fade out on its way rather than vanish wherever it was.
79
+ *
80
+ * The hold is a `debounced` view of `active` that follows it at once on the
81
+ * way up and `exit` ms late on the way down: while the wait runs, the
82
+ * debounced value is still the old `true`, and that is the leaving state.
83
+ * Activity that returns mid-exit supersedes the pending wait, so the band
84
+ * simply carries on. No effect or timer of our own — and no exit on the
85
+ * server, where nothing is painted.
86
+ *
87
+ * Call in an injection context.
88
+ */
89
+ function scanningState(active, exit = SCAN_EXIT) {
90
+ const browser = isPlatformBrowser(inject(PLATFORM_ID));
91
+ const held = debounced(active, (on) => on || !browser ? undefined : new Promise((resolve) => setTimeout(resolve, exit)));
92
+ return computed(() => (active() ? 'true' : held.value() ? 'leaving' : null));
93
+ }
94
+ /** The user has asked for less motion: no fake fill, no sweep. */
95
+ function prefersReducedMotion() {
96
+ return typeof matchMedia === 'function' && matchMedia('(prefers-reduced-motion: reduce)').matches;
97
+ }
98
+
99
+ /**
100
+ * The chip's icon slot: mark an `ng-template` with it (or use the `*`
101
+ * shorthand on the element itself) and the chip renders it in place of the
102
+ * default glyph, in the icon box. The box is `--email-attachment-chip-icon-size`
103
+ * square and sets `font-size` and `color` to match, so a `mat-icon` with
104
+ * `inline`, a font icon, or an SVG drawn at `1em` all fit without a rule.
105
+ */
106
+ class AttachmentChipIcon {
107
+ template = inject(TemplateRef);
108
+ static ngTemplateContextGuard(_directive, context) {
109
+ return true;
110
+ }
111
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: AttachmentChipIcon, deps: [], target: i0.ɵɵFactoryTarget.Directive });
112
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "22.1.6", type: AttachmentChipIcon, isStandalone: true, selector: "ng-template[emailAttachmentIcon]", ngImport: i0 });
113
+ }
114
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: AttachmentChipIcon, decorators: [{
115
+ type: Directive,
116
+ args: [{ selector: 'ng-template[emailAttachmentIcon]' }]
117
+ }] });
118
+ /**
119
+ * The chip's progress slot: the readout in the trailing square, rendered
120
+ * from the host's template instead of the default percentage. The bar along
121
+ * the chip's edge and the scanning sweep stay the chip's — this replaces
122
+ * only what the square shows.
123
+ */
124
+ class AttachmentChipProgress {
125
+ template = inject(TemplateRef);
126
+ static ngTemplateContextGuard(_directive, context) {
127
+ return true;
128
+ }
129
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: AttachmentChipProgress, deps: [], target: i0.ɵɵFactoryTarget.Directive });
130
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "22.1.6", type: AttachmentChipProgress, isStandalone: true, selector: "ng-template[emailAttachmentProgress]", ngImport: i0 });
131
+ }
132
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: AttachmentChipProgress, decorators: [{
133
+ type: Directive,
134
+ args: [{ selector: 'ng-template[emailAttachmentProgress]' }]
135
+ }] });
136
+
137
+ /** A page with its corner folded — the base the document-like kinds draw on. */
138
+ const PAGE = 'M3.5 1.5h5.5l3.5 3.5v9.5h-9z M9 1.5v3.5h3.5';
139
+ /** One stroked path per kind, on a 16×16 grid, drawn in `currentColor`.
140
+ Our own glyphs rather than an icon font: nothing to load, nothing to
141
+ install, and they take the chip's colour like text does. */
142
+ const ICONS = {
143
+ file: PAGE,
144
+ document: `${PAGE} M5.5 8h5 M5.5 10h5 M5.5 12h3`,
145
+ pdf: `${PAGE} M5.5 7.5h5v2.5h-5z M5.5 12h5`,
146
+ spreadsheet: `${PAGE} M5.5 7.5h5v5h-5z M5.5 10h5 M8 7.5v5`,
147
+ presentation: `${PAGE} M6 12.5v-2 M8 12.5v-4.5 M10 12.5v-3`,
148
+ archive: `${PAGE} M7 3h1 M8 4.5h1 M7 6h1 M8 7.5h1 M7 10h2v2.5h-2z`,
149
+ image: 'M2.5 3.5h11v9h-11z M2.5 10.5l3-3 3 3 2-2 3 3 M9.5 6a1 1 0 1 0 2 0a1 1 0 1 0 -2 0',
150
+ video: 'M1.5 4h9v8h-9z M10.5 7l4-2.5v7l-4-2.5',
151
+ audio: 'M6 12V4l7-1.5v8 M3 12a1.5 1.5 0 1 0 3 0a1.5 1.5 0 1 0 -3 0 M10 10.5a1.5 1.5 0 1 0 3 0a1.5 1.5 0 1 0 -3 0',
152
+ message: 'M1.5 3.5h13v9h-13z M1.5 4l6.5 5 6.5-5',
153
+ };
154
+ const DEFAULT_OPTIONS = {
155
+ bytesPerSecond: 10 * 1024 * 1024,
156
+ minDuration: 400,
157
+ maxDuration: 3000,
158
+ fallbackDuration: 800,
159
+ linger: 600,
160
+ };
161
+ /** Overrides for simulated progress, app-wide or per subtree:
162
+ `{ provide: ATTACHMENT_CHIP_OPTIONS, useValue: { bytesPerSecond: 2e6 } }`.
163
+ Anything left out keeps its default. */
164
+ const ATTACHMENT_CHIP_OPTIONS = new InjectionToken('ATTACHMENT_CHIP_OPTIONS', { factory: () => ({}) });
165
+ /** How long a simulated transfer of `size` bytes runs, in ms: size over the
166
+ assumed speed, clamped to the floor and ceiling. The linger after it is
167
+ not part of this — it is the same for every size. */
168
+ function simulatedDuration(size, options = DEFAULT_OPTIONS) {
169
+ if (size == null)
170
+ return options.fallbackDuration;
171
+ const ms = (size / options.bytesPerSecond) * 1000;
172
+ return Math.min(options.maxDuration, Math.max(options.minDuration, ms));
173
+ }
174
+ /** What the progress announces itself as, per stage, unless the host says
175
+ otherwise — "Preparing report.pdf". */
176
+ const STATUS_LABELS = {
177
+ preprocessing: 'Preparing',
178
+ queued: 'Waiting to upload',
179
+ uploading: 'Uploading',
180
+ postprocessing: 'Processing',
181
+ complete: 'Uploaded',
182
+ };
183
+ /**
184
+ * One attachment, laid out like a list row with an avatar:
185
+ *
186
+ * [icon] | name | [progress / ×]
187
+ * | size |
188
+ * ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
189
+ *
190
+ * Progress is a bar along the bottom edge that fills with the number; with
191
+ * no number the whole chip is swept by a band of light instead, the way a
192
+ * scanner passes over a page — and when the number arrives mid-sweep the
193
+ * band fades out on its way rather than vanishing. The square at the end is
194
+ * shared: while work is in flight it holds the readout (the percentage,
195
+ * nothing while there is none), and the remove button paints over it on
196
+ * hover or focus, always on a touch screen. The chip owns all of that
197
+ * motion: the stylesheet draws it and `attachment-chip.animation.ts` times
198
+ * it; a host only says where the transfer is.
199
+ *
200
+ * **Stages are `status`.** A host with an upload pipeline binds the stage
201
+ * it is in — `preprocessing`, `queued`, `uploading`, `postprocessing`,
202
+ * `complete`, the vocabulary of Uppy and tus — and `progress` (0–1) while
203
+ * uploading. The stages where work runs without a number sweep, the one
204
+ * with a number fills, and `queued` keeps quiet: busy, no bar, no sweep,
205
+ * announced as waiting. The progress names itself after the stage
206
+ * ("Preparing report.pdf") unless `progressLabel` says otherwise. The stage
207
+ * is reflected as `data-status` on the host. Which stages a host has, and
208
+ * in what order, is the host's pipeline; the chip only draws the one it is
209
+ * given.
210
+ *
211
+ * **State is ARIA.** `aria-busy` is reflected while any stage is in flight,
212
+ * and is also an input for a host with no stages to tell: busy with a
213
+ * `progress` fills, busy with `null` sweeps. `aria-disabled` takes the
214
+ * remove button out of play. Both inputs go under their ARIA names, so a
215
+ * host writes `[aria-busy]="upload.pending"` exactly as it would on any
216
+ * element, and the chip reflects them back onto itself — they are the
217
+ * styling hooks too (`[email-attachment-chip][aria-busy='true']`).
218
+ *
219
+ * **`simulateProgress`** is for hosts that already have the bytes and only
220
+ * want the "attaching" moment: the chip runs a fill paced by `size` over
221
+ * `ATTACHMENT_CHIP_OPTIONS`, is busy while it runs and for a moment after
222
+ * the bar is full, and settles on its own.
223
+ * It plays once, when the chip is created; it is skipped for reduced motion
224
+ * and on the server; and a real `status` or `aria-busy` from the host always wins. It
225
+ * never emits and never gates anything — don't use it for work that is
226
+ * really happening but untracked; that is `aria-busy` with no `progress`.
227
+ * The fake announces no number to assistive tech, only that it is busy.
228
+ *
229
+ * **Parts are `data-slot`s.** Every part carries one — `icon`, `label`,
230
+ * `name`, `size`, `trailing`, `progress` (the readout, with
231
+ * `progress-value` inside), `progress-track`, `progress-indicator` (the
232
+ * bar), `remove` — and that is how it is styled from outside:
233
+ * `[email-attachment-chip] [data-slot='name'] { … }`. Theme it through the
234
+ * `--email-*` tokens; a structural override reaches a part with two
235
+ * attributes (`[email-attachment-chip] [data-slot='name'][data-slot]`).
236
+ *
237
+ * **Slots are templates.** `icon` and `progress` are boxes the chip sizes;
238
+ * a host fills either with its own template by marking an `ng-template`
239
+ * `emailAttachmentIcon` / `emailAttachmentProgress` (or the `*` shorthand on
240
+ * the element). The icon box is `--email-attachment-chip-icon-size` square
241
+ * and sets `font-size` and `color` to match, so a `mat-icon` with `inline`, a
242
+ * font icon or an SVG drawn at `1em` all fit with no rule of their own; the
243
+ * template gets the attachment and its `kind`. The progress template gets
244
+ * the attachment, `status`, `progress`, `percent` and `mode`, and replaces
245
+ * only the readout — the bar and the sweep stay the chip's.
246
+ *
247
+ * **It asks, it does not do.** The chip holds no list: `removed` is a
248
+ * request the host answers by dropping the item (and cancelling its upload).
249
+ * Picking, dropping, uploading and assembling the MIME are the host's.
250
+ *
251
+ * Opt-in enter/leave animations ship with it:
252
+ * `animate.enter="email-attachment-chip-enter"` and
253
+ * `animate.leave="email-attachment-chip-leave"` on the chip's element.
254
+ *
255
+ * Meant for an `<li>` inside `<ul email-attachment-chips>`. No Material, no
256
+ * icon font, no headless library.
257
+ */
258
+ class AttachmentChip {
259
+ #locale = inject(LOCALE_ID);
260
+ #options = { ...DEFAULT_OPTIONS, ...inject(ATTACHMENT_CHIP_OPTIONS) };
261
+ #browser = isPlatformBrowser(inject(PLATFORM_ID));
262
+ #destroyRef = inject(DestroyRef);
263
+ /** The file this chip stands for. A `File` satisfies it as it is. */
264
+ attachment = input.required(/* @ts-ignore */
265
+ ...(ngDevMode ? [{ debugName: "attachment" }] : /* istanbul ignore next */ []));
266
+ /** Set false for a read-only message (a sent copy, a preview): the chip
267
+ stays, the remove button goes. */
268
+ removable = input(true, { ...(ngDevMode ? { debugName: "removable" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
269
+ /** Prefix for the remove button's accessible name — "Remove report.pdf". */
270
+ removeLabel = input('Remove', /* @ts-ignore */
271
+ ...(ngDevMode ? [{ debugName: "removeLabel" }] : /* istanbul ignore next */ []));
272
+ /** Which stage the transfer is in. `null` for a host with no stages to
273
+ tell — then `aria-busy` and `progress` alone describe the work. */
274
+ status = input(null, /* @ts-ignore */
275
+ ...(ngDevMode ? [{ debugName: "status" }] : /* istanbul ignore next */ []));
276
+ /** Work is in flight for this attachment (an upload, a download, a scan),
277
+ for a host that has no stages to tell. */
278
+ ariaBusy = input(false, { ...(ngDevMode ? { debugName: "ariaBusy" } : /* istanbul ignore next */ {}), alias: 'aria-busy', transform: booleanAttribute });
279
+ /** How far the work is, 0–1, while uploading. `null` when there is no
280
+ number. */
281
+ progress = input(null, /* @ts-ignore */
282
+ ...(ngDevMode ? [{ debugName: "progress" }] : /* istanbul ignore next */ []));
283
+ /** The remove button is out of play — e.g. while the message sends. */
284
+ ariaDisabled = input(false, { ...(ngDevMode ? { debugName: "ariaDisabled" } : /* istanbul ignore next */ {}), alias: 'aria-disabled', transform: booleanAttribute });
285
+ /** Run a fake transfer once, paced by the attachment's size. */
286
+ simulateProgress = input(false, { ...(ngDevMode ? { debugName: "simulateProgress" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
287
+ /** Prefix for the progress's accessible name — "Uploading report.pdf".
288
+ Left out, it follows the stage: Preparing, Uploading, Processing. */
289
+ progressLabel = input(undefined, /* @ts-ignore */
290
+ ...(ngDevMode ? [{ debugName: "progressLabel" }] : /* istanbul ignore next */ []));
291
+ /** The remove button was pressed. The host drops the attachment; the chip
292
+ goes when the host's list no longer has it. */
293
+ removed = output();
294
+ /** The host's own icon, if it handed one in. */
295
+ iconTemplate = contentChild(AttachmentChipIcon, /* @ts-ignore */
296
+ ...(ngDevMode ? [{ debugName: "iconTemplate" }] : /* istanbul ignore next */ []));
297
+ /** The host's own progress readout, if it handed one in. */
298
+ progressTemplate = contentChild(AttachmentChipProgress, /* @ts-ignore */
299
+ ...(ngDevMode ? [{ debugName: "progressTemplate" }] : /* istanbul ignore next */ []));
300
+ #simulating = signal(false, /* @ts-ignore */
301
+ ...(ngDevMode ? [{ debugName: "#simulating" }] : /* istanbul ignore next */ []));
302
+ /** Busy for real (a stage in flight, or the host said so) or for show (a
303
+ simulation running). Public so the container can tell whether any of
304
+ its chips is. */
305
+ busy = computed(() => attachmentInFlight(this.status()) || this.ariaBusy() || this.#simulating(), /* @ts-ignore */
306
+ ...(ngDevMode ? [{ debugName: "busy" }] : /* istanbul ignore next */ []));
307
+ /** Which progress to draw, if any: a sweep for a stage with no number, a
308
+ bar for one with, nothing yet for one that waits, and the host's real
309
+ state beats a simulation. Reflected on the host as `data-mode`. */
310
+ mode = computed(() => {
311
+ const status = this.status();
312
+ if (status === 'preprocessing' || status === 'postprocessing')
313
+ return 'indeterminate';
314
+ if (status === 'queued')
315
+ return 'queued';
316
+ if (status === 'uploading' || this.ariaBusy()) {
317
+ return this.progress() == null ? 'indeterminate' : 'determinate';
318
+ }
319
+ return this.#simulating() ? 'simulated' : null;
320
+ }, /* @ts-ignore */
321
+ ...(ngDevMode ? [{ debugName: "mode" }] : /* istanbul ignore next */ []));
322
+ /** The scanning band's state on the host — `true` while there is no
323
+ number, `leaving` for a moment after one arrives. */
324
+ scanning = scanningState(computed(() => this.mode() === 'indeterminate'));
325
+ /** What the progress is called: the host's word, or the stage's. */
326
+ label = computed(() => this.progressLabel() ?? STATUS_LABELS[this.status() ?? 'uploading'], /* @ts-ignore */
327
+ ...(ngDevMode ? [{ debugName: "label" }] : /* istanbul ignore next */ []));
328
+ percent = computed(() => Math.round(Math.min(1, Math.max(0, this.progress() ?? 0)) * 100), /* @ts-ignore */
329
+ ...(ngDevMode ? [{ debugName: "percent" }] : /* istanbul ignore next */ []));
330
+ kind = computed(() => attachmentKind(this.attachment().type), /* @ts-ignore */
331
+ ...(ngDevMode ? [{ debugName: "kind" }] : /* istanbul ignore next */ []));
332
+ icon = computed(() => ICONS[this.kind()], /* @ts-ignore */
333
+ ...(ngDevMode ? [{ debugName: "icon" }] : /* istanbul ignore next */ []));
334
+ iconContext = computed(() => ({
335
+ $implicit: this.attachment(),
336
+ kind: this.kind(),
337
+ }), /* @ts-ignore */
338
+ ...(ngDevMode ? [{ debugName: "iconContext" }] : /* istanbul ignore next */ []));
339
+ progressContext = computed(() => ({
340
+ $implicit: this.attachment(),
341
+ status: this.status(),
342
+ progress: this.progress(),
343
+ percent: this.percent(),
344
+ mode: this.mode() ?? 'indeterminate',
345
+ }), /* @ts-ignore */
346
+ ...(ngDevMode ? [{ debugName: "progressContext" }] : /* istanbul ignore next */ []));
347
+ size = computed(() => {
348
+ const size = this.attachment().size;
349
+ return size == null ? '' : formatAttachmentSize(size, this.#locale);
350
+ }, /* @ts-ignore */
351
+ ...(ngDevMode ? [{ debugName: "size" }] : /* istanbul ignore next */ []));
352
+ duration = computed(() => simulatedDuration(this.attachment().size, this.#options), /* @ts-ignore */
353
+ ...(ngDevMode ? [{ debugName: "duration" }] : /* istanbul ignore next */ []));
354
+ ngOnInit() {
355
+ if (!this.simulateProgress() || !this.#browser || prefersReducedMotion())
356
+ return;
357
+ // A timer, not `animationend`: the bar's CSS animation is only the
358
+ // picture. A tab that never paints must still see the chip settle.
359
+ this.#simulating.set(true);
360
+ const timer = setTimeout(() => this.#simulating.set(false), this.duration() + this.#options.linger);
361
+ this.#destroyRef.onDestroy(() => clearTimeout(timer));
362
+ }
363
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: AttachmentChip, deps: [], target: i0.ɵɵFactoryTarget.Component });
364
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.6", type: AttachmentChip, isStandalone: true, selector: "[email-attachment-chip]", inputs: { attachment: { classPropertyName: "attachment", publicName: "attachment", isSignal: true, isRequired: true, transformFunction: null }, removable: { classPropertyName: "removable", publicName: "removable", isSignal: true, isRequired: false, transformFunction: null }, removeLabel: { classPropertyName: "removeLabel", publicName: "removeLabel", isSignal: true, isRequired: false, transformFunction: null }, status: { classPropertyName: "status", publicName: "status", isSignal: true, isRequired: false, transformFunction: null }, ariaBusy: { classPropertyName: "ariaBusy", publicName: "aria-busy", isSignal: true, isRequired: false, transformFunction: null }, progress: { classPropertyName: "progress", publicName: "progress", isSignal: true, isRequired: false, transformFunction: null }, ariaDisabled: { classPropertyName: "ariaDisabled", publicName: "aria-disabled", isSignal: true, isRequired: false, transformFunction: null }, simulateProgress: { classPropertyName: "simulateProgress", publicName: "simulateProgress", isSignal: true, isRequired: false, transformFunction: null }, progressLabel: { classPropertyName: "progressLabel", publicName: "progressLabel", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { removed: "removed" }, host: { properties: { "attr.data-kind": "kind()", "attr.data-status": "status()", "attr.data-mode": "mode()", "attr.data-scanning": "scanning()", "attr.aria-busy": "busy() || null", "attr.aria-disabled": "ariaDisabled() || null" } }, queries: [{ propertyName: "iconTemplate", first: true, predicate: AttachmentChipIcon, descendants: true, isSignal: true }, { propertyName: "progressTemplate", first: true, predicate: AttachmentChipProgress, descendants: true, isSignal: true }], ngImport: i0, template: "<!-- Every part carries data-slot: that is its styling hook. The icon box\n and the progress readout are also template slots: each has a default\n the chip draws, and a host replaces it by marking an ng-template with\n emailAttachmentIcon or emailAttachmentProgress \u2014 the chip renders it\n inside the box it already sizes, so the stylesheet never has to reach\n into the host's markup. -->\n<span data-slot=\"icon\">\n @if (iconTemplate(); as slot) {\n <ng-container *ngTemplateOutlet=\"slot.template; context: iconContext()\" />\n } @else {\n <svg viewBox=\"0 0 16 16\" aria-hidden=\"true\">\n <path [attr.d]=\"icon()\" />\n </svg>\n }\n</span>\n\n<span data-slot=\"label\">\n <!-- title, not a tooltip component: the name is truncated in CSS, and the\n native title is what recovers the whole of it. -->\n <span data-slot=\"name\" [title]=\"attachment().name\">{{ attachment().name }}</span>\n @if (size()) {\n <span data-slot=\"size\">{{ size() }}</span>\n }\n</span>\n\n<!-- One square at the end that the progress readout and the remove button\n share: while work is in flight the readout has it, and the \u00D7 paints over\n it on hover or focus (always on a touch screen). The bar under the chip\n is the progress itself; the readout is only its number \u2014 or the host's\n template. With no number the square stays empty and the chip itself is\n swept by a band of light (a host pseudo-element, keyed off data-mode). -->\n<span data-slot=\"trailing\">\n @if (mode(); as m) {\n <!-- A simulated run is announced as busy, never with a number: the\n fake is for the eye, not for assistive tech. -->\n <span\n data-slot=\"progress\"\n [attr.data-mode]=\"m\"\n role=\"progressbar\"\n [attr.aria-label]=\"label() + ' ' + attachment().name\"\n [attr.aria-valuenow]=\"m === 'determinate' ? percent() : null\"\n aria-valuemin=\"0\"\n aria-valuemax=\"100\"\n >\n @if (progressTemplate(); as slot) {\n <ng-container *ngTemplateOutlet=\"slot.template; context: progressContext()\" />\n } @else if (m === 'determinate') {\n <span data-slot=\"progress-value\" aria-hidden=\"true\">{{ percent() }}%</span>\n }\n </span>\n <!-- A waiting transfer has no bar: nothing is moving yet. -->\n @if (m !== 'queued') {\n <span data-slot=\"progress-track\" [attr.data-mode]=\"m\" aria-hidden=\"true\">\n <span\n data-slot=\"progress-indicator\"\n [style.transform]=\"m === 'determinate' ? 'scaleX(' + percent() / 100 + ')' : null\"\n [style.animation-duration.ms]=\"m === 'simulated' ? duration() : null\"\n ></span>\n </span>\n }\n }\n\n @if (removable()) {\n <button\n type=\"button\"\n data-slot=\"remove\"\n [disabled]=\"ariaDisabled()\"\n [attr.aria-label]=\"removeLabel() + ' ' + attachment().name\"\n (click)=\"removed.emit()\"\n >\n <svg viewBox=\"0 0 16 16\" aria-hidden=\"true\">\n <path d=\"M4.5 4.5l7 7 M11.5 4.5l-7 7\" />\n </svg>\n </button>\n }\n</span>\n", styles: [":host{position:relative;display:inline-flex;align-items:center;gap:8px;max-width:var(--email-attachment-chip-max-width, 240px);min-width:0;min-height:40px;margin:0;padding:2px 4px 2px 8px;box-sizing:border-box;background-color:var(--email-attachment-chip-background, var(--mat-sys-surface-container, light-dark(#f1f3f4, #303134)));color:var(--email-attachment-chip-color, var(--mat-sys-on-surface, light-dark(#1f1f1f, #e3e3e3)));border:1px solid var(--email-attachment-chip-border-color, var(--mat-sys-outline-variant, light-dark(#dadce0, #444746)));border-radius:var(--email-attachment-chip-corner, var(--mat-sys-corner-small, 8px));font-family:var(--email-attachment-chip-font-family, var(--mat-sys-label-large-font, system-ui, sans-serif));font-size:var(--email-attachment-chip-font-size, var(--mat-sys-label-large-size, .875rem));font-weight:var(--email-attachment-chip-font-weight, var(--mat-sys-label-large-weight, 500));line-height:var(--email-attachment-chip-line-height, var(--mat-sys-label-large-line-height, 1.25rem));letter-spacing:var(--email-attachment-chip-letter-spacing, var(--mat-sys-label-large-tracking, .006rem));transition:opacity .15s cubic-bezier(.645,.045,.355,1)}:host([aria-disabled=true]){opacity:.6}:host([data-scanning]):after{content:\"\";position:absolute;inset:0;border-radius:inherit;pointer-events:none;background-image:linear-gradient(100deg,transparent 35%,var(--email-attachment-chip-scan-color, color-mix(in srgb, var(--email-attachment-chip-progress-color, var(--mat-sys-primary, light-dark(#0b57d0, #a8c7fa))) 14%, transparent)) 50%,transparent 65%);background-size:300% 100%;background-repeat:no-repeat;animation:email-attachment-chip-scan 2.2s linear infinite,email-attachment-chip-scan-fade 2.2s linear infinite}:host([data-scanning=leaving]):after{animation:email-attachment-chip-scan 2.2s linear infinite,email-attachment-chip-scan-fade 2.2s linear infinite,email-attachment-chip-scan-exit .3s ease-out forwards}:host :where([data-slot=icon]){flex:none;display:inline-flex;align-items:center;justify-content:center;width:var(--email-attachment-chip-icon-size, 20px);height:var(--email-attachment-chip-icon-size, 20px);font-size:var(--email-attachment-chip-icon-size, 20px);line-height:1;color:var(--email-attachment-chip-icon-color, var(--mat-sys-on-surface-variant, light-dark(#5f6368, #9aa0a6)))}:host :where([data-slot=icon]>svg){width:100%;height:100%;fill:none;stroke:currentColor;stroke-width:1.2;stroke-linecap:round;stroke-linejoin:round}:host :where([data-slot=label]){display:flex;flex-direction:column;flex:1 1 auto;min-width:0}:host :where([data-slot=name],[data-slot=size]){overflow:hidden;text-overflow:ellipsis;white-space:nowrap}:host :where([data-slot=size]){font-family:var(--email-attachment-chip-secondary-font-family, var(--mat-sys-body-small-font, system-ui, sans-serif));font-size:var(--email-attachment-chip-secondary-font-size, var(--mat-sys-body-small-size, .75rem));font-weight:var(--email-attachment-chip-secondary-font-weight, var(--mat-sys-body-small-weight, 400));line-height:var(--email-attachment-chip-secondary-line-height, var(--mat-sys-body-small-line-height, 1rem));letter-spacing:var(--email-attachment-chip-secondary-letter-spacing, var(--mat-sys-body-small-tracking, .025rem));color:var(--email-attachment-chip-secondary-color, var(--mat-sys-on-surface-variant, light-dark(#5f6368, #9aa0a6)))}:host :where([data-slot=trailing]){flex:none;display:grid;place-items:center;width:24px;height:24px}:host :where([data-slot=trailing]>*){grid-area:1/1}:host :where([data-slot=progress]){display:flex;align-items:center;justify-content:center;width:24px;height:24px;font-size:var(--email-attachment-chip-progress-size, 1rem);line-height:1;color:var(--email-attachment-chip-secondary-color, var(--mat-sys-on-surface-variant, light-dark(#5f6368, #9aa0a6)))}:host :where([data-slot=progress-value]){font-family:var(--email-attachment-chip-secondary-font-family, var(--mat-sys-body-small-font, system-ui, sans-serif));font-size:.625rem;font-weight:var(--email-attachment-chip-secondary-font-weight, var(--mat-sys-body-small-weight, 400));line-height:1;letter-spacing:-.01em;font-variant-numeric:tabular-nums}:host :where([data-slot=progress-track]){position:absolute;bottom:0;inset-inline:var(--email-attachment-chip-corner, var(--mat-sys-corner-small, 8px));height:2px;overflow:hidden;border-radius:1px;background-color:var(--email-attachment-chip-progress-track, var(--mat-sys-outline-variant, light-dark(#dadce0, #444746)))}:host :where([data-slot=progress-indicator]){display:block;width:100%;height:100%;border-radius:inherit;background-color:var(--email-attachment-chip-progress-color, var(--mat-sys-primary, light-dark(#0b57d0, #a8c7fa)));transform-origin:left;transition:transform .15s linear}:host :where([data-mode=indeterminate] [data-slot=progress-indicator]){transform:scaleX(0)}:host :where([data-mode=simulated] [data-slot=progress-indicator]){animation:email-attachment-chip-fill 1s cubic-bezier(.2,.6,.35,1) forwards}:host :where([data-slot=remove]){display:flex;align-items:center;justify-content:center;width:24px;height:24px;padding:0;border:none;border-radius:50%;cursor:pointer;background:none;color:var(--email-attachment-chip-icon-color, var(--mat-sys-on-surface-variant, light-dark(#5f6368, #9aa0a6)));transition:opacity .12s ease,background-color .12s ease}:host :where([data-slot=remove] svg){width:14px;height:14px;fill:none;stroke:currentColor;stroke-width:1.75;stroke-linecap:round}:host([aria-busy=true]) :where([data-slot=remove]){opacity:0}:host([aria-busy=true]:is(:hover,:focus-within)) :where([data-slot=remove]){opacity:1;background-color:var(--email-attachment-chip-background, var(--mat-sys-surface-container, light-dark(#f1f3f4, #303134)))}@media(hover:none){:host([aria-busy=true]) :where([data-slot=remove]){opacity:1;background-color:var(--email-attachment-chip-background, var(--mat-sys-surface-container, light-dark(#f1f3f4, #303134)))}}:host :where([data-slot=remove]:hover:not(:disabled),[data-slot=remove]:focus-visible){background-color:var(--email-attachment-chip-hover-background, var(--mat-sys-surface-container-highest, light-dark(#e3e3e3, #444746)));color:var(--email-attachment-chip-color, var(--mat-sys-on-surface, light-dark(#1f1f1f, #e3e3e3)))}:host :where([data-slot=remove]:focus-visible){outline:2px solid var(--email-attachment-chip-focus-ring, var(--mat-sys-primary, light-dark(#0b57d0, #a8c7fa)));outline-offset:1px}:host :where([data-slot=remove]:disabled){cursor:default}:host(.email-attachment-chip-enter){animation:email-attachment-chip-enter .16s ease-out}:host(.email-attachment-chip-leave){animation:email-attachment-chip-leave .12s ease-in forwards}@media(prefers-reduced-motion:reduce){:host(.email-attachment-chip-enter),:host(.email-attachment-chip-leave){animation:none}:host([data-scanning]):after{animation:none;background-image:none;background-color:var(--email-attachment-chip-scan-color, color-mix(in srgb, var(--email-attachment-chip-progress-color, var(--mat-sys-primary, light-dark(#0b57d0, #a8c7fa))) 8%, transparent))}}@keyframes email-attachment-chip-scan{0%{background-position:92% 0;animation-timing-function:cubic-bezier(.35,0,.85,1)}55%{background-position:10% 0;animation-timing-function:cubic-bezier(.15,0,.9,.9)}75%{background-position:30% 0;animation-timing-function:cubic-bezier(.1,.1,.3,1)}to{background-position:55% 0}}@keyframes email-attachment-chip-scan-fade{0%,55%{opacity:1}75%{opacity:.85;animation-timing-function:cubic-bezier(.5,0,.75,.5)}96%,to{opacity:0}}@keyframes email-attachment-chip-scan-exit{to{opacity:0}}@keyframes email-attachment-chip-fill{0%{transform:scaleX(0)}to{transform:scaleX(1)}}@keyframes email-attachment-chip-enter{0%{opacity:0;transform:scale(.92)}}@keyframes email-attachment-chip-leave{to{opacity:0;transform:scale(.92)}}\n"], dependencies: [{ kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }] });
365
+ }
366
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: AttachmentChip, decorators: [{
367
+ type: Component,
368
+ args: [{ selector: '[email-attachment-chip]', imports: [NgTemplateOutlet], host: {
369
+ '[attr.data-kind]': 'kind()',
370
+ '[attr.data-status]': 'status()',
371
+ '[attr.data-mode]': 'mode()',
372
+ '[attr.data-scanning]': 'scanning()',
373
+ '[attr.aria-busy]': 'busy() || null',
374
+ '[attr.aria-disabled]': 'ariaDisabled() || null',
375
+ }, template: "<!-- Every part carries data-slot: that is its styling hook. The icon box\n and the progress readout are also template slots: each has a default\n the chip draws, and a host replaces it by marking an ng-template with\n emailAttachmentIcon or emailAttachmentProgress \u2014 the chip renders it\n inside the box it already sizes, so the stylesheet never has to reach\n into the host's markup. -->\n<span data-slot=\"icon\">\n @if (iconTemplate(); as slot) {\n <ng-container *ngTemplateOutlet=\"slot.template; context: iconContext()\" />\n } @else {\n <svg viewBox=\"0 0 16 16\" aria-hidden=\"true\">\n <path [attr.d]=\"icon()\" />\n </svg>\n }\n</span>\n\n<span data-slot=\"label\">\n <!-- title, not a tooltip component: the name is truncated in CSS, and the\n native title is what recovers the whole of it. -->\n <span data-slot=\"name\" [title]=\"attachment().name\">{{ attachment().name }}</span>\n @if (size()) {\n <span data-slot=\"size\">{{ size() }}</span>\n }\n</span>\n\n<!-- One square at the end that the progress readout and the remove button\n share: while work is in flight the readout has it, and the \u00D7 paints over\n it on hover or focus (always on a touch screen). The bar under the chip\n is the progress itself; the readout is only its number \u2014 or the host's\n template. With no number the square stays empty and the chip itself is\n swept by a band of light (a host pseudo-element, keyed off data-mode). -->\n<span data-slot=\"trailing\">\n @if (mode(); as m) {\n <!-- A simulated run is announced as busy, never with a number: the\n fake is for the eye, not for assistive tech. -->\n <span\n data-slot=\"progress\"\n [attr.data-mode]=\"m\"\n role=\"progressbar\"\n [attr.aria-label]=\"label() + ' ' + attachment().name\"\n [attr.aria-valuenow]=\"m === 'determinate' ? percent() : null\"\n aria-valuemin=\"0\"\n aria-valuemax=\"100\"\n >\n @if (progressTemplate(); as slot) {\n <ng-container *ngTemplateOutlet=\"slot.template; context: progressContext()\" />\n } @else if (m === 'determinate') {\n <span data-slot=\"progress-value\" aria-hidden=\"true\">{{ percent() }}%</span>\n }\n </span>\n <!-- A waiting transfer has no bar: nothing is moving yet. -->\n @if (m !== 'queued') {\n <span data-slot=\"progress-track\" [attr.data-mode]=\"m\" aria-hidden=\"true\">\n <span\n data-slot=\"progress-indicator\"\n [style.transform]=\"m === 'determinate' ? 'scaleX(' + percent() / 100 + ')' : null\"\n [style.animation-duration.ms]=\"m === 'simulated' ? duration() : null\"\n ></span>\n </span>\n }\n }\n\n @if (removable()) {\n <button\n type=\"button\"\n data-slot=\"remove\"\n [disabled]=\"ariaDisabled()\"\n [attr.aria-label]=\"removeLabel() + ' ' + attachment().name\"\n (click)=\"removed.emit()\"\n >\n <svg viewBox=\"0 0 16 16\" aria-hidden=\"true\">\n <path d=\"M4.5 4.5l7 7 M11.5 4.5l-7 7\" />\n </svg>\n </button>\n }\n</span>\n", styles: [":host{position:relative;display:inline-flex;align-items:center;gap:8px;max-width:var(--email-attachment-chip-max-width, 240px);min-width:0;min-height:40px;margin:0;padding:2px 4px 2px 8px;box-sizing:border-box;background-color:var(--email-attachment-chip-background, var(--mat-sys-surface-container, light-dark(#f1f3f4, #303134)));color:var(--email-attachment-chip-color, var(--mat-sys-on-surface, light-dark(#1f1f1f, #e3e3e3)));border:1px solid var(--email-attachment-chip-border-color, var(--mat-sys-outline-variant, light-dark(#dadce0, #444746)));border-radius:var(--email-attachment-chip-corner, var(--mat-sys-corner-small, 8px));font-family:var(--email-attachment-chip-font-family, var(--mat-sys-label-large-font, system-ui, sans-serif));font-size:var(--email-attachment-chip-font-size, var(--mat-sys-label-large-size, .875rem));font-weight:var(--email-attachment-chip-font-weight, var(--mat-sys-label-large-weight, 500));line-height:var(--email-attachment-chip-line-height, var(--mat-sys-label-large-line-height, 1.25rem));letter-spacing:var(--email-attachment-chip-letter-spacing, var(--mat-sys-label-large-tracking, .006rem));transition:opacity .15s cubic-bezier(.645,.045,.355,1)}:host([aria-disabled=true]){opacity:.6}:host([data-scanning]):after{content:\"\";position:absolute;inset:0;border-radius:inherit;pointer-events:none;background-image:linear-gradient(100deg,transparent 35%,var(--email-attachment-chip-scan-color, color-mix(in srgb, var(--email-attachment-chip-progress-color, var(--mat-sys-primary, light-dark(#0b57d0, #a8c7fa))) 14%, transparent)) 50%,transparent 65%);background-size:300% 100%;background-repeat:no-repeat;animation:email-attachment-chip-scan 2.2s linear infinite,email-attachment-chip-scan-fade 2.2s linear infinite}:host([data-scanning=leaving]):after{animation:email-attachment-chip-scan 2.2s linear infinite,email-attachment-chip-scan-fade 2.2s linear infinite,email-attachment-chip-scan-exit .3s ease-out forwards}:host :where([data-slot=icon]){flex:none;display:inline-flex;align-items:center;justify-content:center;width:var(--email-attachment-chip-icon-size, 20px);height:var(--email-attachment-chip-icon-size, 20px);font-size:var(--email-attachment-chip-icon-size, 20px);line-height:1;color:var(--email-attachment-chip-icon-color, var(--mat-sys-on-surface-variant, light-dark(#5f6368, #9aa0a6)))}:host :where([data-slot=icon]>svg){width:100%;height:100%;fill:none;stroke:currentColor;stroke-width:1.2;stroke-linecap:round;stroke-linejoin:round}:host :where([data-slot=label]){display:flex;flex-direction:column;flex:1 1 auto;min-width:0}:host :where([data-slot=name],[data-slot=size]){overflow:hidden;text-overflow:ellipsis;white-space:nowrap}:host :where([data-slot=size]){font-family:var(--email-attachment-chip-secondary-font-family, var(--mat-sys-body-small-font, system-ui, sans-serif));font-size:var(--email-attachment-chip-secondary-font-size, var(--mat-sys-body-small-size, .75rem));font-weight:var(--email-attachment-chip-secondary-font-weight, var(--mat-sys-body-small-weight, 400));line-height:var(--email-attachment-chip-secondary-line-height, var(--mat-sys-body-small-line-height, 1rem));letter-spacing:var(--email-attachment-chip-secondary-letter-spacing, var(--mat-sys-body-small-tracking, .025rem));color:var(--email-attachment-chip-secondary-color, var(--mat-sys-on-surface-variant, light-dark(#5f6368, #9aa0a6)))}:host :where([data-slot=trailing]){flex:none;display:grid;place-items:center;width:24px;height:24px}:host :where([data-slot=trailing]>*){grid-area:1/1}:host :where([data-slot=progress]){display:flex;align-items:center;justify-content:center;width:24px;height:24px;font-size:var(--email-attachment-chip-progress-size, 1rem);line-height:1;color:var(--email-attachment-chip-secondary-color, var(--mat-sys-on-surface-variant, light-dark(#5f6368, #9aa0a6)))}:host :where([data-slot=progress-value]){font-family:var(--email-attachment-chip-secondary-font-family, var(--mat-sys-body-small-font, system-ui, sans-serif));font-size:.625rem;font-weight:var(--email-attachment-chip-secondary-font-weight, var(--mat-sys-body-small-weight, 400));line-height:1;letter-spacing:-.01em;font-variant-numeric:tabular-nums}:host :where([data-slot=progress-track]){position:absolute;bottom:0;inset-inline:var(--email-attachment-chip-corner, var(--mat-sys-corner-small, 8px));height:2px;overflow:hidden;border-radius:1px;background-color:var(--email-attachment-chip-progress-track, var(--mat-sys-outline-variant, light-dark(#dadce0, #444746)))}:host :where([data-slot=progress-indicator]){display:block;width:100%;height:100%;border-radius:inherit;background-color:var(--email-attachment-chip-progress-color, var(--mat-sys-primary, light-dark(#0b57d0, #a8c7fa)));transform-origin:left;transition:transform .15s linear}:host :where([data-mode=indeterminate] [data-slot=progress-indicator]){transform:scaleX(0)}:host :where([data-mode=simulated] [data-slot=progress-indicator]){animation:email-attachment-chip-fill 1s cubic-bezier(.2,.6,.35,1) forwards}:host :where([data-slot=remove]){display:flex;align-items:center;justify-content:center;width:24px;height:24px;padding:0;border:none;border-radius:50%;cursor:pointer;background:none;color:var(--email-attachment-chip-icon-color, var(--mat-sys-on-surface-variant, light-dark(#5f6368, #9aa0a6)));transition:opacity .12s ease,background-color .12s ease}:host :where([data-slot=remove] svg){width:14px;height:14px;fill:none;stroke:currentColor;stroke-width:1.75;stroke-linecap:round}:host([aria-busy=true]) :where([data-slot=remove]){opacity:0}:host([aria-busy=true]:is(:hover,:focus-within)) :where([data-slot=remove]){opacity:1;background-color:var(--email-attachment-chip-background, var(--mat-sys-surface-container, light-dark(#f1f3f4, #303134)))}@media(hover:none){:host([aria-busy=true]) :where([data-slot=remove]){opacity:1;background-color:var(--email-attachment-chip-background, var(--mat-sys-surface-container, light-dark(#f1f3f4, #303134)))}}:host :where([data-slot=remove]:hover:not(:disabled),[data-slot=remove]:focus-visible){background-color:var(--email-attachment-chip-hover-background, var(--mat-sys-surface-container-highest, light-dark(#e3e3e3, #444746)));color:var(--email-attachment-chip-color, var(--mat-sys-on-surface, light-dark(#1f1f1f, #e3e3e3)))}:host :where([data-slot=remove]:focus-visible){outline:2px solid var(--email-attachment-chip-focus-ring, var(--mat-sys-primary, light-dark(#0b57d0, #a8c7fa)));outline-offset:1px}:host :where([data-slot=remove]:disabled){cursor:default}:host(.email-attachment-chip-enter){animation:email-attachment-chip-enter .16s ease-out}:host(.email-attachment-chip-leave){animation:email-attachment-chip-leave .12s ease-in forwards}@media(prefers-reduced-motion:reduce){:host(.email-attachment-chip-enter),:host(.email-attachment-chip-leave){animation:none}:host([data-scanning]):after{animation:none;background-image:none;background-color:var(--email-attachment-chip-scan-color, color-mix(in srgb, var(--email-attachment-chip-progress-color, var(--mat-sys-primary, light-dark(#0b57d0, #a8c7fa))) 8%, transparent))}}@keyframes email-attachment-chip-scan{0%{background-position:92% 0;animation-timing-function:cubic-bezier(.35,0,.85,1)}55%{background-position:10% 0;animation-timing-function:cubic-bezier(.15,0,.9,.9)}75%{background-position:30% 0;animation-timing-function:cubic-bezier(.1,.1,.3,1)}to{background-position:55% 0}}@keyframes email-attachment-chip-scan-fade{0%,55%{opacity:1}75%{opacity:.85;animation-timing-function:cubic-bezier(.5,0,.75,.5)}96%,to{opacity:0}}@keyframes email-attachment-chip-scan-exit{to{opacity:0}}@keyframes email-attachment-chip-fill{0%{transform:scaleX(0)}to{transform:scaleX(1)}}@keyframes email-attachment-chip-enter{0%{opacity:0;transform:scale(.92)}}@keyframes email-attachment-chip-leave{to{opacity:0;transform:scale(.92)}}\n"] }]
376
+ }], propDecorators: { attachment: [{ type: i0.Input, args: [{ isSignal: true, alias: "attachment", required: true }] }], removable: [{ type: i0.Input, args: [{ isSignal: true, alias: "removable", required: false }] }], removeLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "removeLabel", required: false }] }], status: [{ type: i0.Input, args: [{ isSignal: true, alias: "status", required: false }] }], ariaBusy: [{ type: i0.Input, args: [{ isSignal: true, alias: "aria-busy", required: false }] }], progress: [{ type: i0.Input, args: [{ isSignal: true, alias: "progress", required: false }] }], ariaDisabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "aria-disabled", required: false }] }], simulateProgress: [{ type: i0.Input, args: [{ isSignal: true, alias: "simulateProgress", required: false }] }], progressLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "progressLabel", required: false }] }], removed: [{ type: i0.Output, args: ["removed"] }], iconTemplate: [{ type: i0.ContentChild, args: [i0.forwardRef(() => AttachmentChipIcon), { isSignal: true }] }], progressTemplate: [{ type: i0.ContentChild, args: [i0.forwardRef(() => AttachmentChipProgress), { isSignal: true }] }] } });
377
+
378
+ /*
379
+ * Public API Surface of @h-k-dev/angular-email-editor/attachment-chip
380
+ */
381
+
382
+ /**
383
+ * Generated bundle index. Do not edit.
384
+ */
385
+
386
+ export { ATTACHMENT_CHIP_OPTIONS, AttachmentChip, AttachmentChipIcon, AttachmentChipProgress, attachmentInFlight, attachmentKind, formatAttachmentSize, simulatedDuration };
387
+ //# sourceMappingURL=h-k-dev-angular-email-editor-attachment-chip.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"h-k-dev-angular-email-editor-attachment-chip.mjs","sources":["../../../projects/angular-email-editor/attachment-chip/attachment.ts","../../../projects/angular-email-editor/attachment-chip/attachment-chip.animation.ts","../../../projects/angular-email-editor/attachment-chip/attachment-chip.slots.ts","../../../projects/angular-email-editor/attachment-chip/attachment-chip.ts","../../../projects/angular-email-editor/attachment-chip/attachment-chip.html","../../../projects/angular-email-editor/attachment-chip/public-api.ts","../../../projects/angular-email-editor/attachment-chip/h-k-dev-angular-email-editor-attachment-chip.ts"],"sourcesContent":["/**\n * What a chip needs to draw an attachment: a name and, if they are known, a\n * MIME type and a size. Deliberately structural rather than a class — a\n * browser `File` already satisfies it, so a host that holds `File[]` binds it\n * straight in with no adapter, and one that holds parsed MIME parts\n * (postal-mime hands back a name, a `mimeType` and the content, not a\n * `File`) maps three fields.\n */\nexport interface Attachment {\n readonly name: string;\n /** MIME type, e.g. `application/pdf`. Only the icon reads it. */\n readonly type?: string;\n /** Size in bytes. Shown under the name, and what simulated progress\n paces itself by. */\n readonly size?: number;\n}\n\n/**\n * Where an attachment's transfer is, in the stages uploaders agree on —\n * Uppy, tus and their kin name them the same way. `preprocessing` is the\n * work before the bytes move (a fingerprint, a virus scan) and has no\n * number; `queued` is accepted and waiting for a connection, with nothing\n * happening yet; `uploading` is the bytes moving, with a number;\n * `postprocessing` is the work after (a transcode, a scan on the far side),\n * again with none; `complete` is done. A chip draws a sweep for the stages\n * where work runs without a number, a bar for the one with, and keeps\n * quiet while a transfer only waits.\n *\n * The set is a vocabulary, not a sequence: which stages a pipeline has, and\n * in what order, is the host's — a store that scans on the far side queues\n * before it preprocesses, one that caps its connections queues after.\n */\nexport type AttachmentStatus =\n 'preprocessing' | 'queued' | 'uploading' | 'postprocessing' | 'complete';\n\n/** The stages during which the transfer is not done — a waiting one\n included, since the message is no more sendable for it. */\nexport function attachmentInFlight(status: AttachmentStatus | null | undefined): boolean {\n return (\n status === 'preprocessing' ||\n status === 'queued' ||\n status === 'uploading' ||\n status === 'postprocessing'\n );\n}\n\n/** The families a chip draws a distinct icon for. */\nexport type AttachmentKind =\n | 'image'\n | 'video'\n | 'audio'\n | 'pdf'\n | 'archive'\n | 'spreadsheet'\n | 'presentation'\n | 'document'\n | 'message'\n | 'file';\n\n/** MIME → kind. Ordered: the first match wins, so the specific document\n types are tested before the `text/*` catch-all, and the Office types —\n which all spell out \"document\" — before the plain document pattern. */\nconst KINDS: readonly (readonly [RegExp, AttachmentKind])[] = [\n [/^image\\//, 'image'],\n [/^video\\//, 'video'],\n [/^audio\\//, 'audio'],\n [/pdf$/, 'pdf'],\n [/(zip|compressed|tar|rar|7z)/, 'archive'],\n [/(spreadsheet|excel|csv)/, 'spreadsheet'],\n [/(presentation|powerpoint)/, 'presentation'],\n [/(word|document|rtf|opendocument\\.text)/, 'document'],\n [/^message\\/|rfc822/, 'message'],\n [/^text\\//, 'document'],\n];\n\n/** The kind of file a MIME type stands for; `file` when the type is unknown\n or matches nothing above. Case-insensitive — a type off the wire may be\n shouted. */\nexport function attachmentKind(type: string | undefined): AttachmentKind {\n if (!type) return 'file';\n const mime = type.toLowerCase();\n for (const [pattern, kind] of KINDS) if (pattern.test(mime)) return kind;\n return 'file';\n}\n\nconst SIZE_UNITS = ['B', 'KB', 'MB', 'GB'] as const;\n\n/**\n * A byte count the way a mail client prints it: binary steps, one decimal\n * below ten (\"2.5 MB\", \"15 MB\"), none for bytes. The number follows the\n * locale (\"2,5 MB\" in German); the unit labels are the ones every mail\n * client uses, not CLDR's (\"kB\", \"byte\").\n */\nexport function formatAttachmentSize(bytes: number, locale = 'en-US'): string {\n let value = Math.max(0, bytes);\n let unit = 0;\n while (value >= 1024 && unit < SIZE_UNITS.length - 1) {\n value /= 1024;\n unit++;\n }\n const digits = unit > 0 && value < 10 ? 1 : 0;\n const number = new Intl.NumberFormat(locale, {\n maximumFractionDigits: digits,\n useGrouping: false,\n }).format(value);\n return `${number} ${SIZE_UNITS[unit]}`;\n}\n","import { isPlatformBrowser } from '@angular/common';\nimport { PLATFORM_ID, Signal, computed, debounced, inject } from '@angular/core';\n\n/**\n * The chip's animation state — the part of the motion that CSS alone cannot\n * carry. The stylesheet owns the pictures (the sweep, the fade, the exit,\n * the fill); this file owns the timing they key off, so the two stay in\n * one component and no host has to know a band is moving.\n */\n\n/** How long the scanning band takes to fade once it is told to go, in ms.\n Matches the stylesheet: its exit animation runs this long, and the host\n attribute that keeps the band on the page holds for the same stretch —\n a tab that never paints must still let go of it. */\nexport const SCAN_EXIT = 300;\n\n/** What the host's `data-scanning` reads: `'true'` while the band sweeps,\n `'leaving'` while it fades out, `null` when there is no band. */\nexport type ScanningState = 'true' | 'leaving' | null;\n\n/**\n * The scanning band's state: `'true'` while `active()`, then `'leaving'`\n * for `exit` ms after it stops, then nothing. The band's CSS keys off both\n * — the sweep runs while the state is set at all, and `leaving` stacks the\n * exit animation on top — so a number that arrives mid-sweep sees the band\n * fade out on its way rather than vanish wherever it was.\n *\n * The hold is a `debounced` view of `active` that follows it at once on the\n * way up and `exit` ms late on the way down: while the wait runs, the\n * debounced value is still the old `true`, and that is the leaving state.\n * Activity that returns mid-exit supersedes the pending wait, so the band\n * simply carries on. No effect or timer of our own — and no exit on the\n * server, where nothing is painted.\n *\n * Call in an injection context.\n */\nexport function scanningState(\n active: Signal<boolean>,\n exit: number = SCAN_EXIT,\n): Signal<ScanningState> {\n const browser = isPlatformBrowser(inject(PLATFORM_ID));\n const held = debounced(active, (on) =>\n on || !browser ? undefined : new Promise<void>((resolve) => setTimeout(resolve, exit)),\n );\n return computed(() => (active() ? 'true' : held.value() ? 'leaving' : null));\n}\n\n/** The user has asked for less motion: no fake fill, no sweep. */\nexport function prefersReducedMotion(): boolean {\n return typeof matchMedia === 'function' && matchMedia('(prefers-reduced-motion: reduce)').matches;\n}\n","import { Directive, TemplateRef, inject } from '@angular/core';\nimport { Attachment, AttachmentKind, AttachmentStatus } from './attachment';\n\n/**\n * The chip's template slots. A host that wants its own icon or its own\n * progress readout hands the chip a template, and the chip renders it\n * inside the box it already sizes — so a Material icon, a font icon or a\n * custom SVG lands in the same 1em square the default glyph does, and the\n * stylesheet never has to reach into the host's markup.\n *\n * <li email-attachment-chip [attachment]=\"file\">\n * <mat-icon *emailAttachmentIcon=\"let kind = kind\" inline>\n * {{ kind === 'pdf' ? 'picture_as_pdf' : 'attach_file' }}\n * </mat-icon>\n * </li>\n */\n\n/** What the icon template is given: the attachment, and the kind the chip\n worked out from its MIME type. */\nexport interface AttachmentChipIconContext {\n readonly $implicit: Attachment;\n readonly kind: AttachmentKind;\n}\n\n/** Which progress the chip is drawing: a bar with a number, a sweep with\n none, the chip's own fake fill, or nothing yet while the transfer waits. */\nexport type AttachmentProgressMode = 'determinate' | 'indeterminate' | 'simulated' | 'queued';\n\n/** What the progress template is given: the attachment and everything the\n chip knows about its transfer. `percent` is `progress` as a whole number\n of 0–100, clamped. */\nexport interface AttachmentChipProgressContext {\n readonly $implicit: Attachment;\n readonly status: AttachmentStatus | null;\n readonly progress: number | null;\n readonly percent: number;\n readonly mode: AttachmentProgressMode;\n}\n\n/**\n * The chip's icon slot: mark an `ng-template` with it (or use the `*`\n * shorthand on the element itself) and the chip renders it in place of the\n * default glyph, in the icon box. The box is `--email-attachment-chip-icon-size`\n * square and sets `font-size` and `color` to match, so a `mat-icon` with\n * `inline`, a font icon, or an SVG drawn at `1em` all fit without a rule.\n */\n@Directive({ selector: 'ng-template[emailAttachmentIcon]' })\nexport class AttachmentChipIcon {\n readonly template = inject<TemplateRef<AttachmentChipIconContext>>(TemplateRef);\n\n static ngTemplateContextGuard(\n _directive: AttachmentChipIcon,\n context: unknown,\n ): context is AttachmentChipIconContext {\n return true;\n }\n}\n\n/**\n * The chip's progress slot: the readout in the trailing square, rendered\n * from the host's template instead of the default percentage. The bar along\n * the chip's edge and the scanning sweep stay the chip's — this replaces\n * only what the square shows.\n */\n@Directive({ selector: 'ng-template[emailAttachmentProgress]' })\nexport class AttachmentChipProgress {\n readonly template = inject<TemplateRef<AttachmentChipProgressContext>>(TemplateRef);\n\n static ngTemplateContextGuard(\n _directive: AttachmentChipProgress,\n context: unknown,\n ): context is AttachmentChipProgressContext {\n return true;\n }\n}\n","import { NgTemplateOutlet, isPlatformBrowser } from '@angular/common';\nimport {\n Component,\n DestroyRef,\n InjectionToken,\n LOCALE_ID,\n OnInit,\n PLATFORM_ID,\n\n // Signals\n booleanAttribute,\n computed,\n contentChild,\n inject,\n input,\n output,\n signal,\n} from '@angular/core';\nimport {\n Attachment,\n AttachmentKind,\n AttachmentStatus,\n attachmentInFlight,\n attachmentKind,\n formatAttachmentSize,\n} from './attachment';\nimport { prefersReducedMotion, scanningState } from './attachment-chip.animation';\nimport {\n AttachmentChipIcon,\n AttachmentChipIconContext,\n AttachmentChipProgress,\n AttachmentChipProgressContext,\n AttachmentProgressMode,\n} from './attachment-chip.slots';\n\n/** A page with its corner folded — the base the document-like kinds draw on. */\nconst PAGE = 'M3.5 1.5h5.5l3.5 3.5v9.5h-9z M9 1.5v3.5h3.5';\n\n/** One stroked path per kind, on a 16×16 grid, drawn in `currentColor`.\n Our own glyphs rather than an icon font: nothing to load, nothing to\n install, and they take the chip's colour like text does. */\nconst ICONS: Record<AttachmentKind, string> = {\n file: PAGE,\n document: `${PAGE} M5.5 8h5 M5.5 10h5 M5.5 12h3`,\n pdf: `${PAGE} M5.5 7.5h5v2.5h-5z M5.5 12h5`,\n spreadsheet: `${PAGE} M5.5 7.5h5v5h-5z M5.5 10h5 M8 7.5v5`,\n presentation: `${PAGE} M6 12.5v-2 M8 12.5v-4.5 M10 12.5v-3`,\n archive: `${PAGE} M7 3h1 M8 4.5h1 M7 6h1 M8 7.5h1 M7 10h2v2.5h-2z`,\n image: 'M2.5 3.5h11v9h-11z M2.5 10.5l3-3 3 3 2-2 3 3 M9.5 6a1 1 0 1 0 2 0a1 1 0 1 0 -2 0',\n video: 'M1.5 4h9v8h-9z M10.5 7l4-2.5v7l-4-2.5',\n audio:\n 'M6 12V4l7-1.5v8 M3 12a1.5 1.5 0 1 0 3 0a1.5 1.5 0 1 0 -3 0 M10 10.5a1.5 1.5 0 1 0 3 0a1.5 1.5 0 1 0 -3 0',\n message: 'M1.5 3.5h13v9h-13z M1.5 4l6.5 5 6.5-5',\n};\n\n/** How simulated progress paces itself. */\nexport interface AttachmentChipOptions {\n /** The connection a simulated transfer pretends to have, in bytes per\n second. A fast line by default — the point is the gesture, not a wait. */\n readonly bytesPerSecond: number;\n /** Floor in ms, so a tiny file still shows a visible fill. */\n readonly minDuration: number;\n /** Ceiling in ms, so a large file never makes anyone wait on a fake. */\n readonly maxDuration: number;\n /** Duration in ms for an attachment whose size is unknown. */\n readonly fallbackDuration: number;\n /** How long the full bar stays before the chip settles, in ms — so 100%\n is something the eye gets to see, not a frame on the way out. */\n readonly linger: number;\n}\n\nconst DEFAULT_OPTIONS: AttachmentChipOptions = {\n bytesPerSecond: 10 * 1024 * 1024,\n minDuration: 400,\n maxDuration: 3000,\n fallbackDuration: 800,\n linger: 600,\n};\n\n/** Overrides for simulated progress, app-wide or per subtree:\n `{ provide: ATTACHMENT_CHIP_OPTIONS, useValue: { bytesPerSecond: 2e6 } }`.\n Anything left out keeps its default. */\nexport const ATTACHMENT_CHIP_OPTIONS = new InjectionToken<Partial<AttachmentChipOptions>>(\n 'ATTACHMENT_CHIP_OPTIONS',\n { factory: () => ({}) },\n);\n\n/** How long a simulated transfer of `size` bytes runs, in ms: size over the\n assumed speed, clamped to the floor and ceiling. The linger after it is\n not part of this — it is the same for every size. */\nexport function simulatedDuration(\n size: number | undefined,\n options: Omit<AttachmentChipOptions, 'linger'> = DEFAULT_OPTIONS,\n): number {\n if (size == null) return options.fallbackDuration;\n const ms = (size / options.bytesPerSecond) * 1000;\n return Math.min(options.maxDuration, Math.max(options.minDuration, ms));\n}\n\n/** What the progress announces itself as, per stage, unless the host says\n otherwise — \"Preparing report.pdf\". */\nconst STATUS_LABELS: Record<AttachmentStatus, string> = {\n preprocessing: 'Preparing',\n queued: 'Waiting to upload',\n uploading: 'Uploading',\n postprocessing: 'Processing',\n complete: 'Uploaded',\n};\n\n/**\n * One attachment, laid out like a list row with an avatar:\n *\n * [icon] | name | [progress / ×]\n * | size |\n * ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔\n *\n * Progress is a bar along the bottom edge that fills with the number; with\n * no number the whole chip is swept by a band of light instead, the way a\n * scanner passes over a page — and when the number arrives mid-sweep the\n * band fades out on its way rather than vanishing. The square at the end is\n * shared: while work is in flight it holds the readout (the percentage,\n * nothing while there is none), and the remove button paints over it on\n * hover or focus, always on a touch screen. The chip owns all of that\n * motion: the stylesheet draws it and `attachment-chip.animation.ts` times\n * it; a host only says where the transfer is.\n *\n * **Stages are `status`.** A host with an upload pipeline binds the stage\n * it is in — `preprocessing`, `queued`, `uploading`, `postprocessing`,\n * `complete`, the vocabulary of Uppy and tus — and `progress` (0–1) while\n * uploading. The stages where work runs without a number sweep, the one\n * with a number fills, and `queued` keeps quiet: busy, no bar, no sweep,\n * announced as waiting. The progress names itself after the stage\n * (\"Preparing report.pdf\") unless `progressLabel` says otherwise. The stage\n * is reflected as `data-status` on the host. Which stages a host has, and\n * in what order, is the host's pipeline; the chip only draws the one it is\n * given.\n *\n * **State is ARIA.** `aria-busy` is reflected while any stage is in flight,\n * and is also an input for a host with no stages to tell: busy with a\n * `progress` fills, busy with `null` sweeps. `aria-disabled` takes the\n * remove button out of play. Both inputs go under their ARIA names, so a\n * host writes `[aria-busy]=\"upload.pending\"` exactly as it would on any\n * element, and the chip reflects them back onto itself — they are the\n * styling hooks too (`[email-attachment-chip][aria-busy='true']`).\n *\n * **`simulateProgress`** is for hosts that already have the bytes and only\n * want the \"attaching\" moment: the chip runs a fill paced by `size` over\n * `ATTACHMENT_CHIP_OPTIONS`, is busy while it runs and for a moment after\n * the bar is full, and settles on its own.\n * It plays once, when the chip is created; it is skipped for reduced motion\n * and on the server; and a real `status` or `aria-busy` from the host always wins. It\n * never emits and never gates anything — don't use it for work that is\n * really happening but untracked; that is `aria-busy` with no `progress`.\n * The fake announces no number to assistive tech, only that it is busy.\n *\n * **Parts are `data-slot`s.** Every part carries one — `icon`, `label`,\n * `name`, `size`, `trailing`, `progress` (the readout, with\n * `progress-value` inside), `progress-track`, `progress-indicator` (the\n * bar), `remove` — and that is how it is styled from outside:\n * `[email-attachment-chip] [data-slot='name'] { … }`. Theme it through the\n * `--email-*` tokens; a structural override reaches a part with two\n * attributes (`[email-attachment-chip] [data-slot='name'][data-slot]`).\n *\n * **Slots are templates.** `icon` and `progress` are boxes the chip sizes;\n * a host fills either with its own template by marking an `ng-template`\n * `emailAttachmentIcon` / `emailAttachmentProgress` (or the `*` shorthand on\n * the element). The icon box is `--email-attachment-chip-icon-size` square\n * and sets `font-size` and `color` to match, so a `mat-icon` with `inline`, a\n * font icon or an SVG drawn at `1em` all fit with no rule of their own; the\n * template gets the attachment and its `kind`. The progress template gets\n * the attachment, `status`, `progress`, `percent` and `mode`, and replaces\n * only the readout — the bar and the sweep stay the chip's.\n *\n * **It asks, it does not do.** The chip holds no list: `removed` is a\n * request the host answers by dropping the item (and cancelling its upload).\n * Picking, dropping, uploading and assembling the MIME are the host's.\n *\n * Opt-in enter/leave animations ship with it:\n * `animate.enter=\"email-attachment-chip-enter\"` and\n * `animate.leave=\"email-attachment-chip-leave\"` on the chip's element.\n *\n * Meant for an `<li>` inside `<ul email-attachment-chips>`. No Material, no\n * icon font, no headless library.\n */\n@Component({\n selector: '[email-attachment-chip]',\n imports: [NgTemplateOutlet],\n templateUrl: './attachment-chip.html',\n styleUrl: './attachment-chip.scss',\n host: {\n '[attr.data-kind]': 'kind()',\n '[attr.data-status]': 'status()',\n '[attr.data-mode]': 'mode()',\n '[attr.data-scanning]': 'scanning()',\n '[attr.aria-busy]': 'busy() || null',\n '[attr.aria-disabled]': 'ariaDisabled() || null',\n },\n})\nexport class AttachmentChip implements OnInit {\n readonly #locale = inject(LOCALE_ID);\n readonly #options = { ...DEFAULT_OPTIONS, ...inject(ATTACHMENT_CHIP_OPTIONS) };\n readonly #browser = isPlatformBrowser(inject(PLATFORM_ID));\n readonly #destroyRef = inject(DestroyRef);\n\n /** The file this chip stands for. A `File` satisfies it as it is. */\n readonly attachment = input.required<Attachment>();\n\n /** Set false for a read-only message (a sent copy, a preview): the chip\n stays, the remove button goes. */\n readonly removable = input(true, { transform: booleanAttribute });\n\n /** Prefix for the remove button's accessible name — \"Remove report.pdf\". */\n readonly removeLabel = input('Remove');\n\n /** Which stage the transfer is in. `null` for a host with no stages to\n tell — then `aria-busy` and `progress` alone describe the work. */\n readonly status = input<AttachmentStatus | null>(null);\n\n /** Work is in flight for this attachment (an upload, a download, a scan),\n for a host that has no stages to tell. */\n readonly ariaBusy = input(false, { alias: 'aria-busy', transform: booleanAttribute });\n\n /** How far the work is, 0–1, while uploading. `null` when there is no\n number. */\n readonly progress = input<number | null>(null);\n\n /** The remove button is out of play — e.g. while the message sends. */\n readonly ariaDisabled = input(false, { alias: 'aria-disabled', transform: booleanAttribute });\n\n /** Run a fake transfer once, paced by the attachment's size. */\n readonly simulateProgress = input(false, { transform: booleanAttribute });\n\n /** Prefix for the progress's accessible name — \"Uploading report.pdf\".\n Left out, it follows the stage: Preparing, Uploading, Processing. */\n readonly progressLabel = input<string | undefined>(undefined);\n\n /** The remove button was pressed. The host drops the attachment; the chip\n goes when the host's list no longer has it. */\n readonly removed = output<void>();\n\n /** The host's own icon, if it handed one in. */\n protected readonly iconTemplate = contentChild(AttachmentChipIcon);\n\n /** The host's own progress readout, if it handed one in. */\n protected readonly progressTemplate = contentChild(AttachmentChipProgress);\n\n readonly #simulating = signal(false);\n\n /** Busy for real (a stage in flight, or the host said so) or for show (a\n simulation running). Public so the container can tell whether any of\n its chips is. */\n readonly busy = computed(\n () => attachmentInFlight(this.status()) || this.ariaBusy() || this.#simulating(),\n );\n\n /** Which progress to draw, if any: a sweep for a stage with no number, a\n bar for one with, nothing yet for one that waits, and the host's real\n state beats a simulation. Reflected on the host as `data-mode`. */\n protected readonly mode = computed<AttachmentProgressMode | null>(() => {\n const status = this.status();\n if (status === 'preprocessing' || status === 'postprocessing') return 'indeterminate';\n if (status === 'queued') return 'queued';\n if (status === 'uploading' || this.ariaBusy()) {\n return this.progress() == null ? 'indeterminate' : 'determinate';\n }\n return this.#simulating() ? 'simulated' : null;\n });\n\n /** The scanning band's state on the host — `true` while there is no\n number, `leaving` for a moment after one arrives. */\n protected readonly scanning = scanningState(computed(() => this.mode() === 'indeterminate'));\n\n /** What the progress is called: the host's word, or the stage's. */\n protected readonly label = computed(\n () => this.progressLabel() ?? STATUS_LABELS[this.status() ?? 'uploading'],\n );\n\n protected readonly percent = computed(() =>\n Math.round(Math.min(1, Math.max(0, this.progress() ?? 0)) * 100),\n );\n\n protected readonly kind = computed(() => attachmentKind(this.attachment().type));\n protected readonly icon = computed(() => ICONS[this.kind()]);\n\n protected readonly iconContext = computed<AttachmentChipIconContext>(() => ({\n $implicit: this.attachment(),\n kind: this.kind(),\n }));\n\n protected readonly progressContext = computed<AttachmentChipProgressContext>(() => ({\n $implicit: this.attachment(),\n status: this.status(),\n progress: this.progress(),\n percent: this.percent(),\n mode: this.mode() ?? 'indeterminate',\n }));\n\n protected readonly size = computed(() => {\n const size = this.attachment().size;\n return size == null ? '' : formatAttachmentSize(size, this.#locale);\n });\n\n protected readonly duration = computed(() =>\n simulatedDuration(this.attachment().size, this.#options),\n );\n\n ngOnInit(): void {\n if (!this.simulateProgress() || !this.#browser || prefersReducedMotion()) return;\n // A timer, not `animationend`: the bar's CSS animation is only the\n // picture. A tab that never paints must still see the chip settle.\n this.#simulating.set(true);\n\n const timer = setTimeout(\n () => this.#simulating.set(false),\n this.duration() + this.#options.linger,\n );\n\n this.#destroyRef.onDestroy(() => clearTimeout(timer));\n }\n}\n","<!-- Every part carries data-slot: that is its styling hook. The icon box\n and the progress readout are also template slots: each has a default\n the chip draws, and a host replaces it by marking an ng-template with\n emailAttachmentIcon or emailAttachmentProgress — the chip renders it\n inside the box it already sizes, so the stylesheet never has to reach\n into the host's markup. -->\n<span data-slot=\"icon\">\n @if (iconTemplate(); as slot) {\n <ng-container *ngTemplateOutlet=\"slot.template; context: iconContext()\" />\n } @else {\n <svg viewBox=\"0 0 16 16\" aria-hidden=\"true\">\n <path [attr.d]=\"icon()\" />\n </svg>\n }\n</span>\n\n<span data-slot=\"label\">\n <!-- title, not a tooltip component: the name is truncated in CSS, and the\n native title is what recovers the whole of it. -->\n <span data-slot=\"name\" [title]=\"attachment().name\">{{ attachment().name }}</span>\n @if (size()) {\n <span data-slot=\"size\">{{ size() }}</span>\n }\n</span>\n\n<!-- One square at the end that the progress readout and the remove button\n share: while work is in flight the readout has it, and the × paints over\n it on hover or focus (always on a touch screen). The bar under the chip\n is the progress itself; the readout is only its number — or the host's\n template. With no number the square stays empty and the chip itself is\n swept by a band of light (a host pseudo-element, keyed off data-mode). -->\n<span data-slot=\"trailing\">\n @if (mode(); as m) {\n <!-- A simulated run is announced as busy, never with a number: the\n fake is for the eye, not for assistive tech. -->\n <span\n data-slot=\"progress\"\n [attr.data-mode]=\"m\"\n role=\"progressbar\"\n [attr.aria-label]=\"label() + ' ' + attachment().name\"\n [attr.aria-valuenow]=\"m === 'determinate' ? percent() : null\"\n aria-valuemin=\"0\"\n aria-valuemax=\"100\"\n >\n @if (progressTemplate(); as slot) {\n <ng-container *ngTemplateOutlet=\"slot.template; context: progressContext()\" />\n } @else if (m === 'determinate') {\n <span data-slot=\"progress-value\" aria-hidden=\"true\">{{ percent() }}%</span>\n }\n </span>\n <!-- A waiting transfer has no bar: nothing is moving yet. -->\n @if (m !== 'queued') {\n <span data-slot=\"progress-track\" [attr.data-mode]=\"m\" aria-hidden=\"true\">\n <span\n data-slot=\"progress-indicator\"\n [style.transform]=\"m === 'determinate' ? 'scaleX(' + percent() / 100 + ')' : null\"\n [style.animation-duration.ms]=\"m === 'simulated' ? duration() : null\"\n ></span>\n </span>\n }\n }\n\n @if (removable()) {\n <button\n type=\"button\"\n data-slot=\"remove\"\n [disabled]=\"ariaDisabled()\"\n [attr.aria-label]=\"removeLabel() + ' ' + attachment().name\"\n (click)=\"removed.emit()\"\n >\n <svg viewBox=\"0 0 16 16\" aria-hidden=\"true\">\n <path d=\"M4.5 4.5l7 7 M11.5 4.5l-7 7\" />\n </svg>\n </button>\n }\n</span>\n","/*\n * Public API Surface of @h-k-dev/angular-email-editor/attachment-chip\n */\n\nexport * from './attachment';\nexport * from './attachment-chip';\nexport * from './attachment-chip.slots';\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":[],"mappings":"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"}
@@ -0,0 +1,80 @@
1
+ import * as i0 from '@angular/core';
2
+ import { input, model, booleanAttribute, contentChildren, computed, effect, Component } from '@angular/core';
3
+ import { AttachmentChip } from '@h-k-dev/angular-email-editor/attachment-chip';
4
+
5
+ /**
6
+ * The row a message's attachment chips sit in — under the body, in Gmail's
7
+ * spot: the strip belongs to the message, not to the envelope.
8
+ *
9
+ * **The strip is the form's control.** It holds the list as its `value` —
10
+ * a `FormValueControl` over the attachments, bound with `[formField]` or
11
+ * two-way as `[(value)]` — and nothing else: the host renders one
12
+ * `<li email-attachment-chip>` per item into it and decides, chip by chip
13
+ * in its own template, what each one shows (real progress, a simulated one,
14
+ * none). That is why the pair is two components. The chips register by
15
+ * being content: each one's `removed` is relayed into the value for as long
16
+ * as it is in the strip, so a chip's × takes its item out of the message
17
+ * with no wiring on the host's side. Identity, not name — the host's items
18
+ * are the value's, so the chip that asked is exactly the item that goes.
19
+ * `disabled` (the form's, or the host's) holds the value still.
20
+ *
21
+ * `aria-busy` is reflected while any chip is busy, so a host can style or
22
+ * gate on the strip as a whole (`[email-attachment-chips][aria-busy='true']`,
23
+ * or a disabled Send).
24
+ *
25
+ * Meant for a `<ul>`. `role="list"` is set anyway, because `list-style: none`
26
+ * costs a list its semantics in Safari. Hidden while it holds no chips, so a
27
+ * host can place it unconditionally.
28
+ */
29
+ class AttachmentChips {
30
+ /** Names the list for assistive tech. */
31
+ label = input('Attachments', /* @ts-ignore */
32
+ ...(ngDevMode ? [{ debugName: "label" }] : /* istanbul ignore next */ []));
33
+ /** The attachments the strip stands for — the form's value. The host
34
+ renders a chip per item; a chip's removal takes its item out of here. */
35
+ value = model([], /* @ts-ignore */
36
+ ...(ngDevMode ? [{ debugName: "value" }] : /* istanbul ignore next */ []));
37
+ /** Nothing may leave the value — e.g. while the message sends. */
38
+ disabled = input(false, { ...(ngDevMode ? { debugName: "disabled" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
39
+ chips = contentChildren(AttachmentChip, /* @ts-ignore */
40
+ ...(ngDevMode ? [{ debugName: "chips" }] : /* istanbul ignore next */ []));
41
+ /** Any chip in the strip is busy — uploading, downloading, simulating. */
42
+ busy = computed(() => this.chips().some((chip) => chip.busy()), /* @ts-ignore */
43
+ ...(ngDevMode ? [{ debugName: "busy" }] : /* istanbul ignore next */ []));
44
+ constructor() {
45
+ effect((onCleanup) => {
46
+ const subscriptions = this.chips().map((chip) => chip.removed.subscribe(() => this.remove(chip.attachment())));
47
+ onCleanup(() => subscriptions.forEach((subscription) => subscription.unsubscribe()));
48
+ });
49
+ }
50
+ /** Takes one attachment out of the value, by identity. A no-op while
51
+ disabled. */
52
+ remove(attachment) {
53
+ if (this.disabled())
54
+ return;
55
+ this.value.update((list) => list.filter((item) => item !== attachment));
56
+ }
57
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: AttachmentChips, deps: [], target: i0.ɵɵFactoryTarget.Component });
58
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.2.0", version: "22.1.6", type: AttachmentChips, isStandalone: true, selector: "[email-attachment-chips]", inputs: { label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange" }, host: { attributes: { "role": "list" }, properties: { "attr.aria-label": "label()", "attr.aria-busy": "busy() || null", "attr.aria-disabled": "disabled() || null", "hidden": "!chips().length" } }, queries: [{ propertyName: "chips", predicate: AttachmentChip, isSignal: true }], ngImport: i0, template: '<ng-content />', isInline: true, styles: [":host{display:flex;flex-wrap:wrap;gap:var(--email-attachment-chips-gap, 6px);flex:none;margin:0;padding-block:0;padding-inline:var(--email-attachment-chips-inset, 16px);list-style:none}:host([hidden]){display:none}\n"] });
59
+ }
60
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: AttachmentChips, decorators: [{
61
+ type: Component,
62
+ args: [{ selector: '[email-attachment-chips]', template: '<ng-content />', host: {
63
+ role: 'list',
64
+ '[attr.aria-label]': 'label()',
65
+ '[attr.aria-busy]': 'busy() || null',
66
+ '[attr.aria-disabled]': 'disabled() || null',
67
+ '[hidden]': '!chips().length',
68
+ }, styles: [":host{display:flex;flex-wrap:wrap;gap:var(--email-attachment-chips-gap, 6px);flex:none;margin:0;padding-block:0;padding-inline:var(--email-attachment-chips-inset, 16px);list-style:none}:host([hidden]){display:none}\n"] }]
69
+ }], ctorParameters: () => [], propDecorators: { label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }, { type: i0.Output, args: ["valueChange"] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], chips: [{ type: i0.ContentChildren, args: [i0.forwardRef(() => AttachmentChip), { isSignal: true }] }] } });
70
+
71
+ /*
72
+ * Public API Surface of @h-k-dev/angular-email-editor/attachment-chips
73
+ */
74
+
75
+ /**
76
+ * Generated bundle index. Do not edit.
77
+ */
78
+
79
+ export { AttachmentChips };
80
+ //# sourceMappingURL=h-k-dev-angular-email-editor-attachment-chips.mjs.map