@h-k-dev/angular-email-editor 0.0.0-placeholder.0 → 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +127 -3
- package/fesm2022/h-k-dev-angular-email-editor-actions.mjs +179 -0
- package/fesm2022/h-k-dev-angular-email-editor-actions.mjs.map +1 -0
- package/fesm2022/h-k-dev-angular-email-editor-address-chip.mjs +285 -0
- package/fesm2022/h-k-dev-angular-email-editor-address-chip.mjs.map +1 -0
- package/fesm2022/h-k-dev-angular-email-editor-address-input.mjs +686 -0
- package/fesm2022/h-k-dev-angular-email-editor-address-input.mjs.map +1 -0
- package/fesm2022/h-k-dev-angular-email-editor-address.mjs +489 -0
- package/fesm2022/h-k-dev-angular-email-editor-address.mjs.map +1 -0
- package/fesm2022/h-k-dev-angular-email-editor-anchor.mjs +64 -0
- package/fesm2022/h-k-dev-angular-email-editor-anchor.mjs.map +1 -0
- package/fesm2022/h-k-dev-angular-email-editor-attachment-chip.mjs +387 -0
- package/fesm2022/h-k-dev-angular-email-editor-attachment-chip.mjs.map +1 -0
- package/fesm2022/h-k-dev-angular-email-editor-attachment-chips.mjs +80 -0
- package/fesm2022/h-k-dev-angular-email-editor-attachment-chips.mjs.map +1 -0
- package/fesm2022/h-k-dev-angular-email-editor-chat-input.mjs +346 -0
- package/fesm2022/h-k-dev-angular-email-editor-chat-input.mjs.map +1 -0
- package/fesm2022/h-k-dev-angular-email-editor-focus.mjs +68 -0
- package/fesm2022/h-k-dev-angular-email-editor-focus.mjs.map +1 -0
- package/fesm2022/h-k-dev-angular-email-editor-image.mjs +132 -0
- package/fesm2022/h-k-dev-angular-email-editor-image.mjs.map +1 -0
- package/fesm2022/h-k-dev-angular-email-editor-material.mjs +124 -0
- package/fesm2022/h-k-dev-angular-email-editor-material.mjs.map +1 -0
- package/fesm2022/h-k-dev-angular-email-editor-metrics.mjs +243 -0
- package/fesm2022/h-k-dev-angular-email-editor-metrics.mjs.map +1 -0
- package/fesm2022/h-k-dev-angular-email-editor-palette.mjs +87 -0
- package/fesm2022/h-k-dev-angular-email-editor-palette.mjs.map +1 -0
- package/fesm2022/h-k-dev-angular-email-editor-proposal.mjs +307 -0
- package/fesm2022/h-k-dev-angular-email-editor-proposal.mjs.map +1 -0
- package/fesm2022/h-k-dev-angular-email-editor-suggestion-menu.mjs +392 -0
- package/fesm2022/h-k-dev-angular-email-editor-suggestion-menu.mjs.map +1 -0
- package/fesm2022/h-k-dev-angular-email-editor.mjs +18818 -0
- package/fesm2022/h-k-dev-angular-email-editor.mjs.map +1 -0
- package/package.json +130 -6
- package/styles/_scroll.scss +101 -0
- package/types/h-k-dev-angular-email-editor-actions.d.ts +147 -0
- package/types/h-k-dev-angular-email-editor-address-chip.d.ts +193 -0
- package/types/h-k-dev-angular-email-editor-address-input.d.ts +328 -0
- package/types/h-k-dev-angular-email-editor-address.d.ts +125 -0
- package/types/h-k-dev-angular-email-editor-anchor.d.ts +44 -0
- package/types/h-k-dev-angular-email-editor-attachment-chip.d.ts +271 -0
- package/types/h-k-dev-angular-email-editor-attachment-chips.d.ts +48 -0
- package/types/h-k-dev-angular-email-editor-chat-input.d.ts +101 -0
- package/types/h-k-dev-angular-email-editor-focus.d.ts +40 -0
- package/types/h-k-dev-angular-email-editor-image.d.ts +66 -0
- package/types/h-k-dev-angular-email-editor-material.d.ts +65 -0
- package/types/h-k-dev-angular-email-editor-metrics.d.ts +110 -0
- package/types/h-k-dev-angular-email-editor-palette.d.ts +51 -0
- package/types/h-k-dev-angular-email-editor-proposal.d.ts +170 -0
- package/types/h-k-dev-angular-email-editor-suggestion-menu.d.ts +177 -0
- package/types/h-k-dev-angular-email-editor.d.ts +3959 -0
|
@@ -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
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"h-k-dev-angular-email-editor-attachment-chips.mjs","sources":["../../../projects/angular-email-editor/attachment-chips/attachment-chips.ts","../../../projects/angular-email-editor/attachment-chips/public-api.ts","../../../projects/angular-email-editor/attachment-chips/h-k-dev-angular-email-editor-attachment-chips.ts"],"sourcesContent":["import {\n Component,\n booleanAttribute,\n computed,\n contentChildren,\n effect,\n input,\n model,\n} from '@angular/core';\nimport type { FormValueControl } from '@angular/forms/signals';\nimport { Attachment, AttachmentChip } from '@h-k-dev/angular-email-editor/attachment-chip';\n\n/**\n * The row a message's attachment chips sit in — under the body, in Gmail's\n * spot: the strip belongs to the message, not to the envelope.\n *\n * **The strip is the form's control.** It holds the list as its `value` —\n * a `FormValueControl` over the attachments, bound with `[formField]` or\n * two-way as `[(value)]` — and nothing else: the host renders one\n * `<li email-attachment-chip>` per item into it and decides, chip by chip\n * in its own template, what each one shows (real progress, a simulated one,\n * none). That is why the pair is two components. The chips register by\n * being content: each one's `removed` is relayed into the value for as long\n * as it is in the strip, so a chip's × takes its item out of the message\n * with no wiring on the host's side. Identity, not name — the host's items\n * are the value's, so the chip that asked is exactly the item that goes.\n * `disabled` (the form's, or the host's) holds the value still.\n *\n * `aria-busy` is reflected while any chip is busy, so a host can style or\n * gate on the strip as a whole (`[email-attachment-chips][aria-busy='true']`,\n * or a disabled Send).\n *\n * Meant for a `<ul>`. `role=\"list\"` is set anyway, because `list-style: none`\n * costs a list its semantics in Safari. Hidden while it holds no chips, so a\n * host can place it unconditionally.\n */\n@Component({\n selector: '[email-attachment-chips]',\n template: '<ng-content />',\n styleUrl: './attachment-chips.scss',\n host: {\n role: 'list',\n '[attr.aria-label]': 'label()',\n '[attr.aria-busy]': 'busy() || null',\n '[attr.aria-disabled]': 'disabled() || null',\n '[hidden]': '!chips().length',\n },\n})\nexport class AttachmentChips<T extends Attachment = Attachment> implements FormValueControl<T[]> {\n /** Names the list for assistive tech. */\n readonly label = input('Attachments');\n\n /** The attachments the strip stands for — the form's value. The host\n renders a chip per item; a chip's removal takes its item out of here. */\n readonly value = model<T[]>([]);\n\n /** Nothing may leave the value — e.g. while the message sends. */\n readonly disabled = input(false, { transform: booleanAttribute });\n\n protected readonly chips = contentChildren(AttachmentChip);\n\n /** Any chip in the strip is busy — uploading, downloading, simulating. */\n readonly busy = computed(() => this.chips().some((chip) => chip.busy()));\n\n constructor() {\n effect((onCleanup) => {\n const subscriptions = this.chips().map((chip) =>\n chip.removed.subscribe(() => this.remove(chip.attachment() as T)),\n );\n onCleanup(() => subscriptions.forEach((subscription) => subscription.unsubscribe()));\n });\n }\n\n /** Takes one attachment out of the value, by identity. A no-op while\n disabled. */\n remove(attachment: T): void {\n if (this.disabled()) return;\n this.value.update((list) => list.filter((item) => item !== attachment));\n }\n}\n","/*\n * Public API Surface of @h-k-dev/angular-email-editor/attachment-chips\n */\n\nexport * from './attachment-chips';\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,346 @@
|
|
|
1
|
+
import * as i0 from '@angular/core';
|
|
2
|
+
import { inject, ElementRef, effect, input, booleanAttribute, model, output, signal, afterNextRender, DestroyRef, untracked, Directive, Component } from '@angular/core';
|
|
3
|
+
import { Plugin, PluginKey } from 'prosemirror-state';
|
|
4
|
+
import { DecorationSet, Decoration } from 'prosemirror-view';
|
|
5
|
+
import { defineExtension, createEditor, Document, Paragraph, Text, HardBreak, BulletList, OrderedList, ListItem, History, NoTextDrag, BaseKeymap } from '@h-k-dev/angular-email-editor';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* The chat input's motion — the part of it CSS alone cannot carry. The
|
|
9
|
+
* stylesheet owns the picture (the `height` transition, its duration and
|
|
10
|
+
* easing on `--email-chat-input-motion`, reduced motion turning it off);
|
|
11
|
+
* this file owns the number it runs to.
|
|
12
|
+
*
|
|
13
|
+
* A height only transitions between lengths, never to or from `auto` — so
|
|
14
|
+
* the text's own height is measured and the box's written in pixels, the
|
|
15
|
+
* way ChatGPT's composer does it. A layout transition: run on the main
|
|
16
|
+
* thread, smooth with GPU acceleration off.
|
|
17
|
+
*/
|
|
18
|
+
/**
|
|
19
|
+
* Keeps the host's `--email-chat-input-height` on the text's height plus
|
|
20
|
+
* the host's padding and border, from a `ResizeObserver` on the editor.
|
|
21
|
+
* Written straight to the host in the observer — before the frame paints,
|
|
22
|
+
* so the transition starts with the line, not a frame behind it. The text
|
|
23
|
+
* sizes itself, capped and scrolling on its own (the stylesheet), so the
|
|
24
|
+
* box never scrolls: no scrollbar flashing while it catches up. Nothing on
|
|
25
|
+
* the server, nor where there is no `ResizeObserver` — the box is `auto`.
|
|
26
|
+
*
|
|
27
|
+
* Call in an injection context, on the chat input's host.
|
|
28
|
+
*/
|
|
29
|
+
function followTextHeight(editor) {
|
|
30
|
+
const host = inject(ElementRef).nativeElement;
|
|
31
|
+
effect((onCleanup) => {
|
|
32
|
+
const current = editor();
|
|
33
|
+
if (!current || typeof ResizeObserver !== 'function')
|
|
34
|
+
return;
|
|
35
|
+
const observer = new ResizeObserver(([entry]) => {
|
|
36
|
+
const text = entry.borderBoxSize?.[0]?.blockSize ?? entry.target.getBoundingClientRect().height;
|
|
37
|
+
const style = getComputedStyle(host);
|
|
38
|
+
const frame = parseFloat(style.paddingTop) +
|
|
39
|
+
parseFloat(style.paddingBottom) +
|
|
40
|
+
parseFloat(style.borderTopWidth) +
|
|
41
|
+
parseFloat(style.borderBottomWidth);
|
|
42
|
+
host.style.setProperty('--email-chat-input-height', `${text + frame}px`);
|
|
43
|
+
});
|
|
44
|
+
observer.observe(current.view.dom);
|
|
45
|
+
onCleanup(() => observer.disconnect());
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** The text as it is sent: a line per block, a list item as a dash line —
|
|
50
|
+
the writer's points, kept as points. */
|
|
51
|
+
function chatInputText(doc) {
|
|
52
|
+
const lines = [];
|
|
53
|
+
doc.descendants((node, _pos, parent) => {
|
|
54
|
+
if (!node.isTextblock)
|
|
55
|
+
return true;
|
|
56
|
+
const text = node.textBetween(0, node.content.size, '\n', ' ').trim();
|
|
57
|
+
if (text)
|
|
58
|
+
lines.push(parent?.type.name === 'listItem' ? `- ${text}` : text);
|
|
59
|
+
return false;
|
|
60
|
+
});
|
|
61
|
+
return lines.join('\n');
|
|
62
|
+
}
|
|
63
|
+
/** Says what the field is for while it is empty: a class and the words on
|
|
64
|
+
its one paragraph, for the stylesheet (`data-placeholder`). */
|
|
65
|
+
const placeholder = (text) => defineExtension({
|
|
66
|
+
name: 'chatInputPlaceholder',
|
|
67
|
+
plugins: () => [
|
|
68
|
+
new Plugin({
|
|
69
|
+
key: new PluginKey('chatInputPlaceholder'),
|
|
70
|
+
props: {
|
|
71
|
+
decorations: (state) => {
|
|
72
|
+
const { doc } = state;
|
|
73
|
+
const first = doc.firstChild;
|
|
74
|
+
const empty = doc.childCount === 1 && !!first?.isTextblock && first.content.size === 0;
|
|
75
|
+
if (!empty || !text())
|
|
76
|
+
return null;
|
|
77
|
+
return DecorationSet.create(doc, [
|
|
78
|
+
Decoration.node(0, first.nodeSize, {
|
|
79
|
+
class: 'email-chat-input__empty',
|
|
80
|
+
'data-placeholder': text(),
|
|
81
|
+
}),
|
|
82
|
+
]);
|
|
83
|
+
},
|
|
84
|
+
},
|
|
85
|
+
}),
|
|
86
|
+
],
|
|
87
|
+
});
|
|
88
|
+
/**
|
|
89
|
+
* The behaviour of a chat input, on an element of the host's own: a field
|
|
90
|
+
* to ask an assistant with, the way a chat's message box works — Enter
|
|
91
|
+
* sends, Shift-Enter breaks the line — that also takes **lists**, so a list
|
|
92
|
+
* of points reaches the assistant as points: `- ` or `1. ` at a line's
|
|
93
|
+
* start begins one, Enter goes on to the next item, Enter on an empty item
|
|
94
|
+
* leaves the list, Tab and Shift-Tab nest and lift. Ctrl-Enter sends from
|
|
95
|
+
* anywhere, a list included. Nothing else: no headings, no bold — an
|
|
96
|
+
* assistant reads words. No look of its own: the host's element, the
|
|
97
|
+
* host's styles (`ChatInput` is the styled one).
|
|
98
|
+
*
|
|
99
|
+
* <div emailChatInput placeholder="Tell the assistant what to change…"
|
|
100
|
+
* [(value)]="instructions" (sent)="ask($event)" (escaped)="close()"></div>
|
|
101
|
+
*
|
|
102
|
+
* `value` is the text as it is sent — a line per block, list items as
|
|
103
|
+
* dash lines (`chatInputText`) — two-way: set it and the field shows it
|
|
104
|
+
* (a dash line becomes an item again). `sent` carries the same text;
|
|
105
|
+
* `clear()` empties the field, `focus()` puts the caret in it. A
|
|
106
|
+
* ProseMirror editor is mounted inside the element (the library's own,
|
|
107
|
+
* `.aee-editor`), so a host's editor styles reach it.
|
|
108
|
+
*
|
|
109
|
+
* **A signal-forms control, optionally.** It is a `FormValueControl` for
|
|
110
|
+
* `string`: bind a field with `[formField]="chat.message"` and the
|
|
111
|
+
* directive drives `value` both ways, pushes `disabled` and `readonly`
|
|
112
|
+
* in, hears `touch` when focus leaves the field, and `reset()` empties
|
|
113
|
+
* it. The form holds the *message*; the ask is still `sent` — an action
|
|
114
|
+
* with a stream behind it is the host's, not the form's submit.
|
|
115
|
+
*/
|
|
116
|
+
class ChatInputField {
|
|
117
|
+
/** What the field is for, while it is empty. */
|
|
118
|
+
placeholder = input('', /* @ts-ignore */
|
|
119
|
+
...(ngDevMode ? [{ debugName: "placeholder" }] : /* istanbul ignore next */ []));
|
|
120
|
+
/** The field's name for assistive technology. */
|
|
121
|
+
label = input('Message', /* @ts-ignore */
|
|
122
|
+
...(ngDevMode ? [{ debugName: "label" }] : /* istanbul ignore next */ []));
|
|
123
|
+
/** Whether typing is refused. */
|
|
124
|
+
disabled = input(false, { ...(ngDevMode ? { debugName: "disabled" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
125
|
+
/** Whether the text is shown but not edited. */
|
|
126
|
+
readonly = input(false, { ...(ngDevMode ? { debugName: "readonly" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
127
|
+
/** The text, as it is sent: lines, and `- ` dash lines for list items. */
|
|
128
|
+
value = model('', /* @ts-ignore */
|
|
129
|
+
...(ngDevMode ? [{ debugName: "value" }] : /* istanbul ignore next */ []));
|
|
130
|
+
/** Enter (outside a list) or Ctrl-Enter: the text, as `value` has it.
|
|
131
|
+
Nothing is sent empty. The field keeps its text — the host clears it
|
|
132
|
+
(`clear()`) once the ask is on its way. */
|
|
133
|
+
sent = output();
|
|
134
|
+
/** Escape, in the field. */
|
|
135
|
+
escaped = output();
|
|
136
|
+
/** Focus left the field — a form marks it touched on this. */
|
|
137
|
+
touch = output();
|
|
138
|
+
#host = inject(ElementRef).nativeElement;
|
|
139
|
+
#editor = signal(null, /* @ts-ignore */
|
|
140
|
+
...(ngDevMode ? [{ debugName: "#editor" }] : /* istanbul ignore next */ []));
|
|
141
|
+
/** The editor inside, once mounted — for a host that wants more of it
|
|
142
|
+
than the text: its selection, a command, its `view`. */
|
|
143
|
+
editor = this.#editor.asReadonly();
|
|
144
|
+
constructor() {
|
|
145
|
+
afterNextRender({ write: () => this.#mount() });
|
|
146
|
+
inject(DestroyRef).onDestroy(() => this.#editor()?.destroy());
|
|
147
|
+
// A value written from outside shows in the field — unless it is what
|
|
148
|
+
// the field just said itself, or the field is being typed in.
|
|
149
|
+
effect(() => {
|
|
150
|
+
const value = this.value();
|
|
151
|
+
const editor = this.#editor();
|
|
152
|
+
if (!editor || editor.view.hasFocus())
|
|
153
|
+
return;
|
|
154
|
+
untracked(() => {
|
|
155
|
+
if (chatInputText(editor.state.doc) !== value)
|
|
156
|
+
this.#show(value);
|
|
157
|
+
});
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
/** Empties the field. */
|
|
161
|
+
clear() {
|
|
162
|
+
this.#editor()?.setText('');
|
|
163
|
+
this.value.set('');
|
|
164
|
+
}
|
|
165
|
+
/** A form's reset: the field, empty. */
|
|
166
|
+
reset() {
|
|
167
|
+
this.clear();
|
|
168
|
+
}
|
|
169
|
+
focus() {
|
|
170
|
+
this.#editor()?.focus();
|
|
171
|
+
}
|
|
172
|
+
#mount() {
|
|
173
|
+
const send = (state, dispatch) => {
|
|
174
|
+
if (!dispatch)
|
|
175
|
+
return true;
|
|
176
|
+
this.#send();
|
|
177
|
+
return true;
|
|
178
|
+
};
|
|
179
|
+
const sendOutsideLists = (state, dispatch) => {
|
|
180
|
+
const { $from } = state.selection;
|
|
181
|
+
for (let depth = $from.depth; depth > 0; depth--) {
|
|
182
|
+
if ($from.node(depth).type.name === 'listItem')
|
|
183
|
+
return false;
|
|
184
|
+
}
|
|
185
|
+
return send(state, dispatch);
|
|
186
|
+
};
|
|
187
|
+
const escape = (_state, dispatch) => {
|
|
188
|
+
if (dispatch)
|
|
189
|
+
this.escaped.emit();
|
|
190
|
+
return true;
|
|
191
|
+
};
|
|
192
|
+
const editor = createEditor({
|
|
193
|
+
parent: this.#host,
|
|
194
|
+
extensions: [
|
|
195
|
+
Document,
|
|
196
|
+
Paragraph,
|
|
197
|
+
Text,
|
|
198
|
+
HardBreak,
|
|
199
|
+
// The lists carry their own keys (Enter goes on to the next item,
|
|
200
|
+
// Tab nests) and their own `- ` / `1. ` input rules.
|
|
201
|
+
BulletList,
|
|
202
|
+
OrderedList,
|
|
203
|
+
ListItem,
|
|
204
|
+
History,
|
|
205
|
+
NoTextDrag,
|
|
206
|
+
defineExtension({
|
|
207
|
+
name: 'chatInputKeys',
|
|
208
|
+
keymap: () => ({
|
|
209
|
+
Enter: sendOutsideLists,
|
|
210
|
+
'Mod-Enter': send,
|
|
211
|
+
'Shift-Enter': (state, dispatch) => {
|
|
212
|
+
const hardBreak = state.schema.nodes['hardBreak'];
|
|
213
|
+
dispatch?.(state.tr.replaceSelectionWith(hardBreak.create()).scrollIntoView());
|
|
214
|
+
return true;
|
|
215
|
+
},
|
|
216
|
+
Escape: escape,
|
|
217
|
+
}),
|
|
218
|
+
}),
|
|
219
|
+
BaseKeymap,
|
|
220
|
+
placeholder(() => this.placeholder()),
|
|
221
|
+
],
|
|
222
|
+
attributes: {
|
|
223
|
+
class: 'email-chat-input__editor',
|
|
224
|
+
role: 'textbox',
|
|
225
|
+
'aria-multiline': 'true',
|
|
226
|
+
'aria-label': this.label(),
|
|
227
|
+
},
|
|
228
|
+
onUpdate: (current) => this.value.set(chatInputText(current.state.doc)),
|
|
229
|
+
});
|
|
230
|
+
editor.view.setProps({
|
|
231
|
+
editable: () => !this.disabled() && !this.readonly(),
|
|
232
|
+
handleDOMEvents: {
|
|
233
|
+
blur: () => {
|
|
234
|
+
this.touch.emit();
|
|
235
|
+
return false;
|
|
236
|
+
},
|
|
237
|
+
},
|
|
238
|
+
});
|
|
239
|
+
this.#editor.set(editor);
|
|
240
|
+
if (this.value())
|
|
241
|
+
this.#show(this.value());
|
|
242
|
+
}
|
|
243
|
+
/** Puts a text in the field: dash lines become list items again. */
|
|
244
|
+
#show(text) {
|
|
245
|
+
const editor = this.#editor();
|
|
246
|
+
if (!editor)
|
|
247
|
+
return;
|
|
248
|
+
const lines = text.split('\n');
|
|
249
|
+
let html = '';
|
|
250
|
+
let inList = false;
|
|
251
|
+
for (const line of lines) {
|
|
252
|
+
const item = /^\s*[-*]\s+(.*)$/.exec(line);
|
|
253
|
+
if (item) {
|
|
254
|
+
if (!inList)
|
|
255
|
+
html += '<ul>';
|
|
256
|
+
html += `<li>${escapeHtml(item[1])}</li>`;
|
|
257
|
+
inList = true;
|
|
258
|
+
}
|
|
259
|
+
else {
|
|
260
|
+
if (inList)
|
|
261
|
+
html += '</ul>';
|
|
262
|
+
inList = false;
|
|
263
|
+
html += `<p>${escapeHtml(line)}</p>`;
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
if (inList)
|
|
267
|
+
html += '</ul>';
|
|
268
|
+
editor.setContent(html);
|
|
269
|
+
}
|
|
270
|
+
#send() {
|
|
271
|
+
const editor = this.#editor();
|
|
272
|
+
if (!editor)
|
|
273
|
+
return;
|
|
274
|
+
const text = chatInputText(editor.state.doc);
|
|
275
|
+
if (!text.trim())
|
|
276
|
+
return;
|
|
277
|
+
this.sent.emit(text);
|
|
278
|
+
}
|
|
279
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: ChatInputField, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
|
280
|
+
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "22.1.6", type: ChatInputField, isStandalone: true, selector: "[emailChatInput]", inputs: { placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, readonly: { classPropertyName: "readonly", publicName: "readonly", isSignal: true, isRequired: false, transformFunction: null }, value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange", sent: "sent", escaped: "escaped", touch: "touch" }, exportAs: ["emailChatInput"], ngImport: i0 });
|
|
281
|
+
}
|
|
282
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: ChatInputField, decorators: [{
|
|
283
|
+
type: Directive,
|
|
284
|
+
args: [{ selector: '[emailChatInput]', exportAs: 'emailChatInput' }]
|
|
285
|
+
}], ctorParameters: () => [], propDecorators: { placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], readonly: [{ type: i0.Input, args: [{ isSignal: true, alias: "readonly", required: false }] }], value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }, { type: i0.Output, args: ["valueChange"] }], sent: [{ type: i0.Output, args: ["sent"] }], escaped: [{ type: i0.Output, args: ["escaped"] }], touch: [{ type: i0.Output, args: ["touch"] }] } });
|
|
286
|
+
/**
|
|
287
|
+
* The chat input, styled: `ChatInputField`'s behaviour in a box of its
|
|
288
|
+
* own — outlined on focus, growing and shrinking with its text in a short
|
|
289
|
+
* transition, scrolling past a height, disabled dimmed — the look
|
|
290
|
+
* tokenized (`--email-chat-input-*`, Material's system tokens beneath).
|
|
291
|
+
* The same inputs and outputs, forwarded; the same signal-forms contract.
|
|
292
|
+
*
|
|
293
|
+
* <div email-chat-input placeholder="Tell the assistant what to change…"
|
|
294
|
+
* [(value)]="instructions" (sent)="ask($event)" (escaped)="close()"></div>
|
|
295
|
+
*
|
|
296
|
+
* `field` is the directive underneath; `editor`, `clear()` and `focus()`
|
|
297
|
+
* are its, at hand.
|
|
298
|
+
*
|
|
299
|
+
* **The growing** (chat-input.animation.ts): the box follows the text's
|
|
300
|
+
* measured height through a `height` transition — `--email-chat-input-motion`
|
|
301
|
+
* is its duration and easing (`150ms ease`); reduced motion turns it off. The
|
|
302
|
+
* text grows to `--email-chat-input-max-height` (`5lh`, five lines) and
|
|
303
|
+
* then scrolls inside the box.
|
|
304
|
+
*/
|
|
305
|
+
class ChatInput {
|
|
306
|
+
/** The behaviour underneath: the field itself. */
|
|
307
|
+
field = inject(ChatInputField);
|
|
308
|
+
/** The editor inside, once mounted. */
|
|
309
|
+
editor = this.field.editor;
|
|
310
|
+
constructor() {
|
|
311
|
+
followTextHeight(this.editor);
|
|
312
|
+
}
|
|
313
|
+
/** Empties the field. */
|
|
314
|
+
clear() {
|
|
315
|
+
this.field.clear();
|
|
316
|
+
}
|
|
317
|
+
focus() {
|
|
318
|
+
this.field.focus();
|
|
319
|
+
}
|
|
320
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: ChatInput, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
321
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "22.1.6", type: ChatInput, isStandalone: true, selector: "div[email-chat-input]", host: { properties: { "class.email-chat-input--disabled": "field.disabled()" } }, hostDirectives: [{ directive: ChatInputField, inputs: ["placeholder", "placeholder", "label", "label", "disabled", "disabled", "readonly", "readonly", "value", "value"], outputs: ["valueChange", "valueChange", "sent", "sent", "escaped", "escaped", "touch", "touch"] }], ngImport: i0, template: '', isInline: true, styles: [":host{display:block;box-sizing:border-box;min-width:0;height:var(--email-chat-input-height, auto);overflow:hidden;transition:height var(--email-chat-input-motion, .15s ease);padding:var(--email-chat-input-padding, 6px 10px);color:var(--email-chat-input-color, var(--mat-sys-on-surface, light-dark(#1f1f1f, #e3e3e3)));background-color:var(--email-chat-input-background, var(--mat-sys-surface-bright, light-dark(#ffffff, #2b2c2e)));border-radius:var(--email-chat-input-corner, var(--mat-sys-corner-small, 4px));font:var(--email-chat-input-font, var(--mat-sys-body-medium, inherit))}:host:focus-within{outline:2px solid var(--email-chat-input-focus, var(--mat-sys-primary, #0056b3));outline-offset:-2px}@media(prefers-reduced-motion:reduce){:host{transition:none}}:host(.email-chat-input--disabled){opacity:.6}:host ::ng-deep .email-chat-input__editor{outline:none;height:auto;max-width:none;max-height:var(--email-chat-input-max-height, 5lh);overflow-y:auto;--_email-scroll-size: var(--email-scroll-size, 6px);--_email-scroll-thumb: var( --email-scroll-thumb, color-mix(in srgb, var(--mat-sys-outline-variant, light-dark(#c4c7c5, #444746)) 50%, transparent) );--_email-scroll-thumb-hover: var( --email-scroll-thumb-hover, color-mix(in srgb, var(--mat-sys-outline-variant, light-dark(#c4c7c5, #444746)) 75%, transparent) );--_email-scroll-current: transparent;scrollbar-gutter:stable;overscroll-behavior-x:contain}@supports (-moz-appearance: none){:host ::ng-deep .email-chat-input__editor{scrollbar-width:thin;scrollbar-color:var(--_email-scroll-current) var(--email-scroll-track, transparent)}}:host ::ng-deep .email-chat-input__editor::-webkit-scrollbar{width:var(--_email-scroll-size);height:var(--_email-scroll-size)}:host ::ng-deep .email-chat-input__editor::-webkit-scrollbar-track{background:var(--email-scroll-track, transparent)}:host ::ng-deep .email-chat-input__editor::-webkit-scrollbar-corner{background:transparent}:host ::ng-deep .email-chat-input__editor::-webkit-scrollbar-thumb{background-color:var(--_email-scroll-current);border-radius:1000px}:host ::ng-deep .email-chat-input__editor:hover,:host ::ng-deep .email-chat-input__editor:focus-within{--_email-scroll-current: var(--_email-scroll-thumb)}:host ::ng-deep .email-chat-input__editor:hover::-webkit-scrollbar-thumb:hover,:host ::ng-deep .email-chat-input__editor:focus-within::-webkit-scrollbar-thumb:hover{background-color:var(--_email-scroll-thumb-hover)}@media(hover:none){:host ::ng-deep .email-chat-input__editor{--_email-scroll-current: var(--_email-scroll-thumb)}}:host ::ng-deep .email-chat-input__editor p,:host ::ng-deep .email-chat-input__editor ul,:host ::ng-deep .email-chat-input__editor ol{margin:0}:host ::ng-deep .email-chat-input__editor ul,:host ::ng-deep .email-chat-input__editor ol{padding-inline-start:20px}:host ::ng-deep .email-chat-input__editor .email-chat-input__empty:before{content:attr(data-placeholder);position:absolute;pointer-events:none;color:var(--email-chat-input-placeholder-color, var(--mat-sys-on-surface-variant, light-dark(#777777, #9a9a9a)))}\n"] });
|
|
322
|
+
}
|
|
323
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: ChatInput, decorators: [{
|
|
324
|
+
type: Component,
|
|
325
|
+
args: [{ selector: 'div[email-chat-input]', template: '', hostDirectives: [
|
|
326
|
+
{
|
|
327
|
+
directive: ChatInputField,
|
|
328
|
+
inputs: ['placeholder', 'label', 'disabled', 'readonly', 'value'],
|
|
329
|
+
outputs: ['valueChange', 'sent', 'escaped', 'touch'],
|
|
330
|
+
},
|
|
331
|
+
], host: { '[class.email-chat-input--disabled]': 'field.disabled()' }, styles: [":host{display:block;box-sizing:border-box;min-width:0;height:var(--email-chat-input-height, auto);overflow:hidden;transition:height var(--email-chat-input-motion, .15s ease);padding:var(--email-chat-input-padding, 6px 10px);color:var(--email-chat-input-color, var(--mat-sys-on-surface, light-dark(#1f1f1f, #e3e3e3)));background-color:var(--email-chat-input-background, var(--mat-sys-surface-bright, light-dark(#ffffff, #2b2c2e)));border-radius:var(--email-chat-input-corner, var(--mat-sys-corner-small, 4px));font:var(--email-chat-input-font, var(--mat-sys-body-medium, inherit))}:host:focus-within{outline:2px solid var(--email-chat-input-focus, var(--mat-sys-primary, #0056b3));outline-offset:-2px}@media(prefers-reduced-motion:reduce){:host{transition:none}}:host(.email-chat-input--disabled){opacity:.6}:host ::ng-deep .email-chat-input__editor{outline:none;height:auto;max-width:none;max-height:var(--email-chat-input-max-height, 5lh);overflow-y:auto;--_email-scroll-size: var(--email-scroll-size, 6px);--_email-scroll-thumb: var( --email-scroll-thumb, color-mix(in srgb, var(--mat-sys-outline-variant, light-dark(#c4c7c5, #444746)) 50%, transparent) );--_email-scroll-thumb-hover: var( --email-scroll-thumb-hover, color-mix(in srgb, var(--mat-sys-outline-variant, light-dark(#c4c7c5, #444746)) 75%, transparent) );--_email-scroll-current: transparent;scrollbar-gutter:stable;overscroll-behavior-x:contain}@supports (-moz-appearance: none){:host ::ng-deep .email-chat-input__editor{scrollbar-width:thin;scrollbar-color:var(--_email-scroll-current) var(--email-scroll-track, transparent)}}:host ::ng-deep .email-chat-input__editor::-webkit-scrollbar{width:var(--_email-scroll-size);height:var(--_email-scroll-size)}:host ::ng-deep .email-chat-input__editor::-webkit-scrollbar-track{background:var(--email-scroll-track, transparent)}:host ::ng-deep .email-chat-input__editor::-webkit-scrollbar-corner{background:transparent}:host ::ng-deep .email-chat-input__editor::-webkit-scrollbar-thumb{background-color:var(--_email-scroll-current);border-radius:1000px}:host ::ng-deep .email-chat-input__editor:hover,:host ::ng-deep .email-chat-input__editor:focus-within{--_email-scroll-current: var(--_email-scroll-thumb)}:host ::ng-deep .email-chat-input__editor:hover::-webkit-scrollbar-thumb:hover,:host ::ng-deep .email-chat-input__editor:focus-within::-webkit-scrollbar-thumb:hover{background-color:var(--_email-scroll-thumb-hover)}@media(hover:none){:host ::ng-deep .email-chat-input__editor{--_email-scroll-current: var(--_email-scroll-thumb)}}:host ::ng-deep .email-chat-input__editor p,:host ::ng-deep .email-chat-input__editor ul,:host ::ng-deep .email-chat-input__editor ol{margin:0}:host ::ng-deep .email-chat-input__editor ul,:host ::ng-deep .email-chat-input__editor ol{padding-inline-start:20px}:host ::ng-deep .email-chat-input__editor .email-chat-input__empty:before{content:attr(data-placeholder);position:absolute;pointer-events:none;color:var(--email-chat-input-placeholder-color, var(--mat-sys-on-surface-variant, light-dark(#777777, #9a9a9a)))}\n"] }]
|
|
332
|
+
}], ctorParameters: () => [] });
|
|
333
|
+
function escapeHtml(text) {
|
|
334
|
+
return text.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
/*
|
|
338
|
+
* Public API Surface of @h-k-dev/angular-email-editor/chat-input
|
|
339
|
+
*/
|
|
340
|
+
|
|
341
|
+
/**
|
|
342
|
+
* Generated bundle index. Do not edit.
|
|
343
|
+
*/
|
|
344
|
+
|
|
345
|
+
export { ChatInput, ChatInputField, chatInputText };
|
|
346
|
+
//# sourceMappingURL=h-k-dev-angular-email-editor-chat-input.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"h-k-dev-angular-email-editor-chat-input.mjs","sources":["../../../projects/angular-email-editor/chat-input/chat-input.animation.ts","../../../projects/angular-email-editor/chat-input/chat-input.ts","../../../projects/angular-email-editor/chat-input/public-api.ts","../../../projects/angular-email-editor/chat-input/h-k-dev-angular-email-editor-chat-input.ts"],"sourcesContent":["import { ElementRef, Signal, effect, inject } from '@angular/core';\nimport type { Editor } from '@h-k-dev/angular-email-editor';\n\n/**\n * The chat input's motion — the part of it CSS alone cannot carry. The\n * stylesheet owns the picture (the `height` transition, its duration and\n * easing on `--email-chat-input-motion`, reduced motion turning it off);\n * this file owns the number it runs to.\n *\n * A height only transitions between lengths, never to or from `auto` — so\n * the text's own height is measured and the box's written in pixels, the\n * way ChatGPT's composer does it. A layout transition: run on the main\n * thread, smooth with GPU acceleration off.\n */\n\n/**\n * Keeps the host's `--email-chat-input-height` on the text's height plus\n * the host's padding and border, from a `ResizeObserver` on the editor.\n * Written straight to the host in the observer — before the frame paints,\n * so the transition starts with the line, not a frame behind it. The text\n * sizes itself, capped and scrolling on its own (the stylesheet), so the\n * box never scrolls: no scrollbar flashing while it catches up. Nothing on\n * the server, nor where there is no `ResizeObserver` — the box is `auto`.\n *\n * Call in an injection context, on the chat input's host.\n */\nexport function followTextHeight(editor: Signal<Editor | null>): void {\n const host = inject<ElementRef<HTMLElement>>(ElementRef).nativeElement;\n effect((onCleanup) => {\n const current = editor();\n if (!current || typeof ResizeObserver !== 'function') return;\n const observer = new ResizeObserver(([entry]) => {\n const text = entry.borderBoxSize?.[0]?.blockSize ?? entry.target.getBoundingClientRect().height;\n const style = getComputedStyle(host);\n const frame =\n parseFloat(style.paddingTop) +\n parseFloat(style.paddingBottom) +\n parseFloat(style.borderTopWidth) +\n parseFloat(style.borderBottomWidth);\n host.style.setProperty('--email-chat-input-height', `${text + frame}px`);\n });\n observer.observe(current.view.dom);\n onCleanup(() => observer.disconnect());\n });\n}\n","import {\n Component,\n DestroyRef,\n Directive,\n ElementRef,\n afterNextRender,\n booleanAttribute,\n effect,\n inject,\n input,\n model,\n output,\n signal,\n untracked,\n} from '@angular/core';\nimport type { FormValueControl } from '@angular/forms/signals';\nimport { Command, Plugin, PluginKey } from 'prosemirror-state';\nimport { Node as ProseMirrorNode } from 'prosemirror-model';\nimport { Decoration, DecorationSet } from 'prosemirror-view';\nimport {\n BaseKeymap,\n BulletList,\n Document,\n Editor,\n Extension,\n HardBreak,\n History,\n ListItem,\n NoTextDrag,\n OrderedList,\n Paragraph,\n Text,\n createEditor,\n defineExtension,\n} from '@h-k-dev/angular-email-editor';\nimport { followTextHeight } from './chat-input.animation';\n\n/** The text as it is sent: a line per block, a list item as a dash line —\n the writer's points, kept as points. */\nexport function chatInputText(doc: ProseMirrorNode): string {\n const lines: string[] = [];\n doc.descendants((node, _pos, parent) => {\n if (!node.isTextblock) return true;\n const text = node.textBetween(0, node.content.size, '\\n', ' ').trim();\n if (text) lines.push(parent?.type.name === 'listItem' ? `- ${text}` : text);\n return false;\n });\n return lines.join('\\n');\n}\n\n/** Says what the field is for while it is empty: a class and the words on\n its one paragraph, for the stylesheet (`data-placeholder`). */\nconst placeholder = (text: () => string): Extension =>\n defineExtension({\n name: 'chatInputPlaceholder',\n plugins: () => [\n new Plugin({\n key: new PluginKey('chatInputPlaceholder'),\n props: {\n decorations: (state) => {\n const { doc } = state;\n const first = doc.firstChild;\n const empty = doc.childCount === 1 && !!first?.isTextblock && first.content.size === 0;\n if (!empty || !text()) return null;\n return DecorationSet.create(doc, [\n Decoration.node(0, first!.nodeSize, {\n class: 'email-chat-input__empty',\n 'data-placeholder': text(),\n }),\n ]);\n },\n },\n }),\n ],\n });\n\n/**\n * The behaviour of a chat input, on an element of the host's own: a field\n * to ask an assistant with, the way a chat's message box works — Enter\n * sends, Shift-Enter breaks the line — that also takes **lists**, so a list\n * of points reaches the assistant as points: `- ` or `1. ` at a line's\n * start begins one, Enter goes on to the next item, Enter on an empty item\n * leaves the list, Tab and Shift-Tab nest and lift. Ctrl-Enter sends from\n * anywhere, a list included. Nothing else: no headings, no bold — an\n * assistant reads words. No look of its own: the host's element, the\n * host's styles (`ChatInput` is the styled one).\n *\n * <div emailChatInput placeholder=\"Tell the assistant what to change…\"\n * [(value)]=\"instructions\" (sent)=\"ask($event)\" (escaped)=\"close()\"></div>\n *\n * `value` is the text as it is sent — a line per block, list items as\n * dash lines (`chatInputText`) — two-way: set it and the field shows it\n * (a dash line becomes an item again). `sent` carries the same text;\n * `clear()` empties the field, `focus()` puts the caret in it. A\n * ProseMirror editor is mounted inside the element (the library's own,\n * `.aee-editor`), so a host's editor styles reach it.\n *\n * **A signal-forms control, optionally.** It is a `FormValueControl` for\n * `string`: bind a field with `[formField]=\"chat.message\"` and the\n * directive drives `value` both ways, pushes `disabled` and `readonly`\n * in, hears `touch` when focus leaves the field, and `reset()` empties\n * it. The form holds the *message*; the ask is still `sent` — an action\n * with a stream behind it is the host's, not the form's submit.\n */\n@Directive({ selector: '[emailChatInput]', exportAs: 'emailChatInput' })\nexport class ChatInputField implements FormValueControl<string> {\n /** What the field is for, while it is empty. */\n readonly placeholder = input('');\n\n /** The field's name for assistive technology. */\n readonly label = input('Message');\n\n /** Whether typing is refused. */\n readonly disabled = input(false, { transform: booleanAttribute });\n\n /** Whether the text is shown but not edited. */\n readonly readonly = input(false, { transform: booleanAttribute });\n\n /** The text, as it is sent: lines, and `- ` dash lines for list items. */\n readonly value = model('');\n\n /** Enter (outside a list) or Ctrl-Enter: the text, as `value` has it.\n Nothing is sent empty. The field keeps its text — the host clears it\n (`clear()`) once the ask is on its way. */\n readonly sent = output<string>();\n\n /** Escape, in the field. */\n readonly escaped = output<void>();\n\n /** Focus left the field — a form marks it touched on this. */\n readonly touch = output<void>();\n\n readonly #host = inject<ElementRef<HTMLElement>>(ElementRef).nativeElement;\n\n readonly #editor = signal<Editor | null>(null);\n\n /** The editor inside, once mounted — for a host that wants more of it\n than the text: its selection, a command, its `view`. */\n readonly editor = this.#editor.asReadonly();\n\n constructor() {\n afterNextRender({ write: () => this.#mount() });\n inject(DestroyRef).onDestroy(() => this.#editor()?.destroy());\n // A value written from outside shows in the field — unless it is what\n // the field just said itself, or the field is being typed in.\n effect(() => {\n const value = this.value();\n const editor = this.#editor();\n if (!editor || editor.view.hasFocus()) return;\n untracked(() => {\n if (chatInputText(editor.state.doc) !== value) this.#show(value);\n });\n });\n }\n\n /** Empties the field. */\n clear(): void {\n this.#editor()?.setText('');\n this.value.set('');\n }\n\n /** A form's reset: the field, empty. */\n reset(): void {\n this.clear();\n }\n\n focus(): void {\n this.#editor()?.focus();\n }\n\n #mount(): void {\n const send: Command = (state, dispatch) => {\n if (!dispatch) return true;\n this.#send();\n return true;\n };\n const sendOutsideLists: Command = (state, dispatch) => {\n const { $from } = state.selection;\n for (let depth = $from.depth; depth > 0; depth--) {\n if ($from.node(depth).type.name === 'listItem') return false;\n }\n return send(state, dispatch);\n };\n const escape: Command = (_state, dispatch) => {\n if (dispatch) this.escaped.emit();\n return true;\n };\n const editor = createEditor({\n parent: this.#host,\n extensions: [\n Document,\n Paragraph,\n Text,\n HardBreak,\n // The lists carry their own keys (Enter goes on to the next item,\n // Tab nests) and their own `- ` / `1. ` input rules.\n BulletList,\n OrderedList,\n ListItem,\n History,\n NoTextDrag,\n defineExtension({\n name: 'chatInputKeys',\n keymap: () => ({\n Enter: sendOutsideLists,\n 'Mod-Enter': send,\n 'Shift-Enter': (state, dispatch) => {\n const hardBreak = state.schema.nodes['hardBreak'];\n dispatch?.(state.tr.replaceSelectionWith(hardBreak.create()).scrollIntoView());\n return true;\n },\n Escape: escape,\n }),\n }),\n BaseKeymap,\n placeholder(() => this.placeholder()),\n ],\n attributes: {\n class: 'email-chat-input__editor',\n role: 'textbox',\n 'aria-multiline': 'true',\n 'aria-label': this.label(),\n },\n onUpdate: (current) => this.value.set(chatInputText(current.state.doc)),\n });\n editor.view.setProps({\n editable: () => !this.disabled() && !this.readonly(),\n handleDOMEvents: {\n blur: () => {\n this.touch.emit();\n return false;\n },\n },\n });\n this.#editor.set(editor);\n if (this.value()) this.#show(this.value());\n }\n\n /** Puts a text in the field: dash lines become list items again. */\n #show(text: string): void {\n const editor = this.#editor();\n if (!editor) return;\n const lines = text.split('\\n');\n let html = '';\n let inList = false;\n for (const line of lines) {\n const item = /^\\s*[-*]\\s+(.*)$/.exec(line);\n if (item) {\n if (!inList) html += '<ul>';\n html += `<li>${escapeHtml(item[1])}</li>`;\n inList = true;\n } else {\n if (inList) html += '</ul>';\n inList = false;\n html += `<p>${escapeHtml(line)}</p>`;\n }\n }\n if (inList) html += '</ul>';\n editor.setContent(html);\n }\n\n #send(): void {\n const editor = this.#editor();\n if (!editor) return;\n const text = chatInputText(editor.state.doc);\n if (!text.trim()) return;\n this.sent.emit(text);\n }\n}\n\n/**\n * The chat input, styled: `ChatInputField`'s behaviour in a box of its\n * own — outlined on focus, growing and shrinking with its text in a short\n * transition, scrolling past a height, disabled dimmed — the look\n * tokenized (`--email-chat-input-*`, Material's system tokens beneath).\n * The same inputs and outputs, forwarded; the same signal-forms contract.\n *\n * <div email-chat-input placeholder=\"Tell the assistant what to change…\"\n * [(value)]=\"instructions\" (sent)=\"ask($event)\" (escaped)=\"close()\"></div>\n *\n * `field` is the directive underneath; `editor`, `clear()` and `focus()`\n * are its, at hand.\n *\n * **The growing** (chat-input.animation.ts): the box follows the text's\n * measured height through a `height` transition — `--email-chat-input-motion`\n * is its duration and easing (`150ms ease`); reduced motion turns it off. The\n * text grows to `--email-chat-input-max-height` (`5lh`, five lines) and\n * then scrolls inside the box.\n */\n@Component({\n selector: 'div[email-chat-input]',\n template: '',\n styleUrl: './chat-input.scss',\n hostDirectives: [\n {\n directive: ChatInputField,\n inputs: ['placeholder', 'label', 'disabled', 'readonly', 'value'],\n outputs: ['valueChange', 'sent', 'escaped', 'touch'],\n },\n ],\n host: { '[class.email-chat-input--disabled]': 'field.disabled()' },\n})\nexport class ChatInput {\n /** The behaviour underneath: the field itself. */\n readonly field = inject(ChatInputField);\n\n /** The editor inside, once mounted. */\n readonly editor = this.field.editor;\n\n constructor() {\n followTextHeight(this.editor);\n }\n\n /** Empties the field. */\n clear(): void {\n this.field.clear();\n }\n\n focus(): void {\n this.field.focus();\n }\n}\n\nfunction escapeHtml(text: string): string {\n return text.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');\n}\n","/*\n * Public API Surface of @h-k-dev/angular-email-editor/chat-input\n */\n\nexport * from './chat-input';\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":[],"mappings":"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"}
|