@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.
Files changed (51) hide show
  1. package/README.md +127 -3
  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 +285 -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 +686 -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 +68 -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 +18818 -0
  33. package/fesm2022/h-k-dev-angular-email-editor.mjs.map +1 -0
  34. package/package.json +130 -6
  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 +193 -0
  38. package/types/h-k-dev-angular-email-editor-address-input.d.ts +328 -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 +40 -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 @@
1
+ {"version":3,"file":"h-k-dev-angular-email-editor-metrics.mjs","sources":["../../../projects/angular-email-editor/metrics/measurement.ts","../../../projects/angular-email-editor/metrics/text-metrics.ts","../../../projects/angular-email-editor/metrics/public-api.ts","../../../projects/angular-email-editor/metrics/h-k-dev-angular-email-editor-metrics.ts"],"sourcesContent":["import { Node as PMNode } from 'prosemirror-model';\nimport { PreparedText, layout, prepare } from '@chenglou/pretext';\nimport {\n PreparedRichInline,\n RichInlineItem,\n measureRichInlineStats,\n prepareRichInline,\n} from '@chenglou/pretext/rich-inline';\n\n/**\n * Math-based text measurement for the editor document, built on\n * `@chenglou/pretext`. Block heights and line counts are computed from cached\n * canvas glyph widths instead of DOM reads (`offsetHeight`,\n * `getBoundingClientRect`), so measuring never triggers a layout reflow.\n */\n\n/** Typography of the live editor surface, read from computed style once. */\nexport interface EditorTypography {\n fontFamily: string;\n /** Body text size in px. */\n fontSize: number;\n fontWeight: number;\n fontStyle: string;\n /** Body line height in px. */\n lineHeight: number;\n /** Vertical gap between sibling top-level blocks (`--prose-gap`), px. */\n blockGap: number;\n /** Heading font-size factors for levels 1-6, relative to the body size. */\n headingScale: readonly number[];\n /** Horizontal space consumed by wrapper blocks, px. */\n indents: {\n blockquote: number;\n list: number;\n /** Vertical gap between adjacent list items, px. */\n listItemGap: number;\n };\n}\n\n/** Browser default h1-h6 sizes; the editor stylesheet doesn't restyle them. */\nconst HEADING_SCALE = [2, 1.5, 1.17, 1, 0.83, 0.67] as const;\n\nconst cssPx = (style: CSSStyleDeclaration, property: string, fallback: number): number => {\n const value = parseFloat(style.getPropertyValue(property));\n return Number.isFinite(value) ? value : fallback;\n};\n\n/**\n * Reads the typography pretext needs from the editor's computed style. One\n * DOM read at startup (and again once webfonts settle) — never per keystroke.\n */\nexport function readTypography(dom: HTMLElement): EditorTypography {\n const style = getComputedStyle(dom);\n const fontSize = parseFloat(style.fontSize) || 16;\n // `line-height: normal` parses as NaN; browsers render it at roughly 1.2.\n const lineHeight = parseFloat(style.lineHeight) || fontSize * 1.2;\n const innerSpacing = cssPx(style, '--mat-sys-inner-spacing', 16);\n\n return {\n // Pretext refuses a font string the canvas would ignore: an editor with\n // no family in its computed style (detached, unstyled) measures in the\n // browser's sans-serif rather than not at all.\n fontFamily: style.fontFamily.trim() || 'sans-serif',\n fontSize,\n fontWeight: parseInt(style.fontWeight, 10) || 400,\n fontStyle: style.fontStyle || 'normal',\n lineHeight,\n blockGap: cssPx(style, '--prose-gap', fontSize),\n headingScale: HEADING_SCALE,\n indents: {\n blockquote: innerSpacing + 3, // padding-inline-start + border\n list: 1.5 * fontSize, // ul/ol padding-inline-start\n listItemGap: 0.25 * fontSize, // li + li margin-block-start\n },\n };\n}\n\nexport interface TextMetrics {\n words: number;\n characters: number;\n /** Wrapped line count across all text blocks at the current width. */\n lines: number;\n /**\n * Estimated rendered height of the document in px, block gaps included.\n * Text-only: atom blocks (images) contribute no height since their\n * rendered size isn't knowable without loading them.\n */\n estimatedHeight: number;\n}\n\n/**\n * Prepared measurements for one textblock. Single-font blocks (the common\n * case in email) go through `prepare()` with `pre-wrap`, matching the editor\n * CSS exactly — hard breaks become `\\n` in one prepared handle. Blocks with\n * mark-dependent fonts (bold/italic) use the rich-inline API, one prepared\n * handle per hard-break segment — `pre-wrap` too, so a bold word's line\n * breaks and spaces count exactly as the plain line's.\n */\ntype PreparedBlock =\n { kind: 'plain'; prepared: PreparedText } | { kind: 'rich'; segments: PreparedRichInline[] };\n\ninterface CachedBlock {\n block: PreparedBlock;\n lineHeight: number;\n words: number;\n characters: number;\n /** Line count memoised for the last width measured at. */\n lastWidth: number;\n lastLines: number;\n}\n\n/**\n * Measurement cache keyed on ProseMirror's immutable nodes: blocks untouched\n * by a transaction keep their node identity, so across keystrokes only the\n * edited block is ever re-prepared. Create one per editor view; replace it\n * wholesale to invalidate (e.g. after webfonts load).\n */\nexport type BlockMeasureCache = WeakMap<PMNode, CachedBlock>;\n\nexport const createBlockMeasureCache = (): BlockMeasureCache => new WeakMap();\n\n/**\n * Measures the whole document at the given content width. Pure arithmetic\n * over cached glyph widths — no DOM access.\n */\nexport function measureDoc(\n doc: PMNode,\n contentWidth: number,\n typography: EditorTypography,\n cache: BlockMeasureCache,\n): TextMetrics {\n const metrics: TextMetrics = { words: 0, characters: 0, lines: 0, estimatedHeight: 0 };\n const width = Math.max(contentWidth, 1);\n\n const visitChildren = (parent: PMNode, availableWidth: number, gap: number) => {\n let index = 0;\n parent.forEach((child) => {\n if (index++ > 0) metrics.estimatedHeight += gap;\n visit(child, availableWidth);\n });\n };\n\n const visit = (node: PMNode, availableWidth: number) => {\n if (node.isTextblock) {\n const measured = measureTextblock(node, availableWidth, typography, cache);\n metrics.words += measured.words;\n metrics.characters += measured.characters;\n metrics.lines += measured.lastLines;\n metrics.estimatedHeight += measured.lastLines * measured.lineHeight;\n return;\n }\n\n switch (node.type.name) {\n case 'blockquote':\n visitChildren(node, availableWidth - typography.indents.blockquote, 0);\n break;\n case 'bulletList':\n case 'orderedList':\n visitChildren(\n node,\n availableWidth - typography.indents.list,\n typography.indents.listItemGap,\n );\n break;\n default:\n // List items and future wrappers: measure content at full width.\n if (node.childCount) visitChildren(node, availableWidth, 0);\n break;\n }\n };\n\n visitChildren(doc, width, typography.blockGap);\n metrics.estimatedHeight = Math.round(metrics.estimatedHeight);\n return metrics;\n}\n\nfunction measureTextblock(\n node: PMNode,\n width: number,\n typography: EditorTypography,\n cache: BlockMeasureCache,\n): CachedBlock {\n const maxWidth = Math.max(width, 1);\n\n let cached = cache.get(node);\n if (!cached) {\n cached = prepareBlock(node, typography);\n cache.set(node, cached);\n }\n\n if (cached.lastWidth !== maxWidth) {\n cached.lastLines = countLines(cached.block, maxWidth, cached.lineHeight);\n cached.lastWidth = maxWidth;\n }\n\n return cached;\n}\n\nfunction countLines(block: PreparedBlock, maxWidth: number, lineHeight: number): number {\n if (block.kind === 'plain') {\n // Browsers size an empty block to one line-height; pretext reports 0.\n return Math.max(1, layout(block.prepared, maxWidth, lineHeight).lineCount);\n }\n return block.segments.reduce(\n (lines, segment) => lines + Math.max(1, measureRichInlineStats(segment, maxWidth).lineCount),\n 0,\n );\n}\n\ninterface BlockFont {\n size: number;\n weight: number;\n style: string;\n lineHeight: number;\n}\n\nfunction blockFontFor(node: PMNode, t: EditorTypography): BlockFont {\n if (node.type.name === 'heading') {\n const level: number = node.attrs['level'] ?? 1;\n const size = t.fontSize * (t.headingScale[level - 1] ?? 1);\n return {\n size,\n weight: 700,\n style: t.fontStyle,\n lineHeight: size * (t.lineHeight / t.fontSize),\n };\n }\n return { size: t.fontSize, weight: t.fontWeight, style: t.fontStyle, lineHeight: t.lineHeight };\n}\n\n/** Canvas `font` shorthand, the format pretext's `prepare()` expects. */\nconst fontShorthand = (style: string, weight: number, size: number, family: string): string =>\n `${style === 'normal' ? '' : `${style} `}${weight} ${size}px ${family}`;\n\nfunction prepareBlock(node: PMNode, typography: EditorTypography): CachedBlock {\n const base = blockFontFor(node, typography);\n const baseFont = fontShorthand(base.style, base.weight, base.size, typography.fontFamily);\n\n // Inline runs split on hard breaks; each segment lays out as its own line(s).\n const segments: RichInlineItem[][] = [[]];\n let words = 0;\n let characters = 0;\n let mixedFonts = false;\n\n node.forEach((child) => {\n if (child.type.name === 'hardBreak') {\n segments.push([]);\n return;\n }\n if (!child.isText || !child.text) return;\n\n const bold = child.marks.some((mark) => mark.type.name === 'bold');\n const italic = child.marks.some((mark) => mark.type.name === 'italic');\n const font =\n bold || italic\n ? fontShorthand(\n italic ? 'italic' : base.style,\n bold ? 700 : base.weight,\n base.size,\n typography.fontFamily,\n )\n : baseFont;\n\n if (font !== baseFont) mixedFonts = true;\n segments[segments.length - 1].push({ text: child.text, font });\n words += child.text.split(/\\s+/).filter(Boolean).length;\n characters += child.text.length;\n });\n\n const block: PreparedBlock = mixedFonts\n ? {\n kind: 'rich',\n segments: segments.map((runs) => prepareRichInline(runs, { whiteSpace: 'pre-wrap' })),\n }\n : {\n // Single font: `prepare()` with pre-wrap mirrors the editor CSS\n // exactly, and hard breaks fold into the text as `\\n`.\n kind: 'plain',\n prepared: prepare(\n segments.map((runs) => runs.map((run) => run.text).join('')).join('\\n'),\n baseFont,\n { whiteSpace: 'pre-wrap' },\n ),\n };\n\n return { block, lineHeight: base.lineHeight, words, characters, lastWidth: -1, lastLines: 0 };\n}\n","import { EditorState, Plugin, PluginKey } from 'prosemirror-state';\nimport { EditorView } from 'prosemirror-view';\nimport { FunctionalExtension, defineExtension } from '@h-k-dev/angular-email-editor';\nimport { TextMetrics, createBlockMeasureCache, measureDoc, readTypography } from './measurement';\n\nexport interface TextMetricsOptions {\n /** Called with fresh metrics after every doc change and width resize. */\n onMetrics: (metrics: TextMetrics) => void;\n /** Called when a measurement fails — no canvas to measure with (a server\n render, jsdom), or a font the browser cannot use. The editor goes on\n unharmed; that measurement reports nothing, and the next change, or\n the web fonts arriving, tries again. */\n onError?: (error: unknown) => void;\n}\n\n/**\n * Live document metrics (words, lines, estimated height) computed with\n * `@chenglou/pretext` — pure arithmetic over cached glyph widths, no DOM\n * measurement on the keystroke path. Unchanged blocks are never re-measured:\n * the cache keys on ProseMirror's immutable nodes.\n *\n * Metrics are an extra, never a dependency of the editor: a measurement\n * that throws is reported through `onError` and skipped, and without a\n * `ResizeObserver` the width read at mount stands.\n */\nexport const createTextMetrics = (options: TextMetricsOptions): FunctionalExtension =>\n defineExtension({\n name: 'textMetrics',\n plugins: () => [\n new Plugin({\n key: new PluginKey('textMetrics'),\n view: (view) => createTextMetricsView(view, options),\n }),\n ],\n });\n\nfunction createTextMetricsView(view: EditorView, { onMetrics, onError }: TextMetricsOptions) {\n let typography = readTypography(view.dom);\n let cache = createBlockMeasureCache();\n // One startup read; afterwards the observer reports widths, so resizes\n // and keystrokes alike never force a layout reflow.\n let width = view.dom.clientWidth;\n let destroyed = false;\n\n const measure = (currentView: EditorView = view) => {\n let metrics: TextMetrics;\n try {\n metrics = measureDoc(currentView.state.doc, width, typography, cache);\n } catch (error) {\n onError?.(error);\n return;\n }\n onMetrics(metrics);\n };\n\n const observer =\n typeof ResizeObserver === 'undefined'\n ? null\n : new ResizeObserver((entries) => {\n const next = entries[entries.length - 1].contentRect.width;\n if (next === width || next === 0) return;\n width = next;\n // Prepared glyph widths survive a resize; only the line arithmetic reruns.\n measure();\n });\n observer?.observe(view.dom);\n\n // Canvas measurements taken before webfonts finish loading are off;\n // re-prepare everything once the real fonts are in.\n document.fonts?.ready.then(() => {\n if (destroyed) return;\n typography = readTypography(view.dom);\n cache = createBlockMeasureCache();\n measure();\n });\n\n measure();\n\n return {\n update(currentView: EditorView, prevState?: EditorState) {\n if (prevState && prevState.doc.eq(currentView.state.doc)) return;\n measure(currentView);\n },\n destroy() {\n destroyed = true;\n observer?.disconnect();\n },\n };\n}\n","/*\n * Public API Surface of @h-k-dev/angular-email-editor/metrics\n *\n * Its own entry because it is the only part of the library that needs\n * `@chenglou/pretext` — an optional peer: a host without word counts never\n * installs it.\n */\n\nexport * from './measurement';\nexport * from './text-metrics';\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":[],"mappings":"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"}
@@ -0,0 +1,87 @@
1
+ import { InjectionToken, inject } from '@angular/core';
2
+ import { emailBackgroundPalette, emailTextPalette } from '@h-k-dev/angular-email-editor';
3
+
4
+ /** The library's own palette — the swatches that pass on both a light and a
5
+ dark client (`emailTextPalette`, `emailBackgroundPalette`). */
6
+ const emailPalette = {
7
+ text: emailTextPalette,
8
+ background: emailBackgroundPalette,
9
+ };
10
+ /**
11
+ * The palette in use — the library's unless a host provides its own
12
+ * ({@link providePalette}). What the colour pickers offer and what the `/`
13
+ * menu's colour rows are made of ({@link colorSuggestions}).
14
+ */
15
+ const EMAIL_PALETTE = new InjectionToken('EMAIL_PALETTE', {
16
+ providedIn: 'root',
17
+ factory: () => emailPalette,
18
+ });
19
+ /** The palette a config comes to, over the library's. */
20
+ function resolvePalette(config) {
21
+ const given = typeof config === 'function' ? config(emailPalette) : config;
22
+ return {
23
+ text: given.text ?? emailPalette.text,
24
+ background: given.background ?? emailPalette.background,
25
+ };
26
+ }
27
+ /**
28
+ * Makes a palette the app's — in `appConfig.providers`, or a component's:
29
+ *
30
+ * providePalette({ text: BRAND_TEXT_COLORS })
31
+ * providePalette((defaults) => ({
32
+ * text: [...defaults.text, { name: 'Brand', value: '#0a5c36' }],
33
+ * }))
34
+ *
35
+ * Every colour goes into an email as written: keep to what a client shows
36
+ * on both a light and a dark page (`passesDualContrast`).
37
+ */
38
+ function providePalette(config) {
39
+ return { provide: EMAIL_PALETTE, useFactory: () => resolvePalette(config) };
40
+ }
41
+ /** The palette in use, in an injection context. */
42
+ function injectPalette() {
43
+ return inject(EMAIL_PALETTE);
44
+ }
45
+ /** The word a colour's name becomes in an id: lowercased, spaces as hyphens. */
46
+ const slug = (name) => name.trim().toLowerCase().split(/\s+/).join('-');
47
+ /**
48
+ * The palette as rows of a `/` menu, the way a chat's slash menu lists
49
+ * colours: "Red text", "Red background" — one row per colour of either
50
+ * side, in the section `color`, each carrying its `swatch`. Picked, a row
51
+ * colours the selection, or what is typed next at a bare caret (the
52
+ * `TextStyle` mark's `setColor` / `setBackgroundColor`); nothing, when the
53
+ * kit has no `TextStyle`.
54
+ *
55
+ * items: (ctx) => [...extensionSuggestions(ctx), ...colorSuggestions(ctx, palette)]
56
+ */
57
+ function colorSuggestions(ctx, palette) {
58
+ const textStyle = ctx.extensions.find((extension) => extension.name === 'textStyle');
59
+ const commands = textStyle?.commands?.(ctx);
60
+ const setColor = commands?.['setColor'];
61
+ const setBackground = commands?.['setBackgroundColor'];
62
+ if (!setColor || !setBackground)
63
+ return [];
64
+ const rows = (colors, kind, run) => colors.map((color) => ({
65
+ id: `${kind}-${slug(color.name)}`,
66
+ title: kind === 'color' ? `${color.name} text` : `${color.name} background`,
67
+ keywords: ['color', 'colour', kind === 'color' ? 'text' : 'highlight', color.name],
68
+ section: 'color',
69
+ swatch: color.value,
70
+ command: run(color.value),
71
+ }));
72
+ return [
73
+ ...rows(palette.text, 'color', (value) => setColor(value)),
74
+ ...rows(palette.background, 'background', (value) => setBackground(value)),
75
+ ];
76
+ }
77
+
78
+ /*
79
+ * Public API Surface of @h-k-dev/angular-email-editor/palette
80
+ */
81
+
82
+ /**
83
+ * Generated bundle index. Do not edit.
84
+ */
85
+
86
+ export { EMAIL_PALETTE, colorSuggestions, emailPalette, injectPalette, providePalette, resolvePalette };
87
+ //# sourceMappingURL=h-k-dev-angular-email-editor-palette.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"h-k-dev-angular-email-editor-palette.mjs","sources":["../../../projects/angular-email-editor/palette/palette.ts","../../../projects/angular-email-editor/palette/public-api.ts","../../../projects/angular-email-editor/palette/h-k-dev-angular-email-editor-palette.ts"],"sourcesContent":["import { InjectionToken, Provider, inject } from '@angular/core';\nimport {\n ExtensionContext,\n PaletteColor,\n SuggestionCommandItem,\n emailBackgroundPalette,\n emailTextPalette,\n} from '@h-k-dev/angular-email-editor';\n\n/** The colours a host offers: for text, and for a background. */\nexport interface EmailPalette {\n readonly text: readonly PaletteColor[];\n readonly background: readonly PaletteColor[];\n}\n\n/** The library's own palette — the swatches that pass on both a light and a\n dark client (`emailTextPalette`, `emailBackgroundPalette`). */\nexport const emailPalette: EmailPalette = {\n text: emailTextPalette,\n background: emailBackgroundPalette,\n};\n\n/** A host's palette: either list, the other staying the library's — or a\n function of the library's palette, to mix: keep it and add a brand\n colour, take a few out, replace one side. */\nexport type EmailPaletteConfig =\n Partial<EmailPalette> | ((defaults: EmailPalette) => Partial<EmailPalette>);\n\n/**\n * The palette in use — the library's unless a host provides its own\n * ({@link providePalette}). What the colour pickers offer and what the `/`\n * menu's colour rows are made of ({@link colorSuggestions}).\n */\nexport const EMAIL_PALETTE = new InjectionToken<EmailPalette>('EMAIL_PALETTE', {\n providedIn: 'root',\n factory: () => emailPalette,\n});\n\n/** The palette a config comes to, over the library's. */\nexport function resolvePalette(config: EmailPaletteConfig): EmailPalette {\n const given = typeof config === 'function' ? config(emailPalette) : config;\n return {\n text: given.text ?? emailPalette.text,\n background: given.background ?? emailPalette.background,\n };\n}\n\n/**\n * Makes a palette the app's — in `appConfig.providers`, or a component's:\n *\n * providePalette({ text: BRAND_TEXT_COLORS })\n * providePalette((defaults) => ({\n * text: [...defaults.text, { name: 'Brand', value: '#0a5c36' }],\n * }))\n *\n * Every colour goes into an email as written: keep to what a client shows\n * on both a light and a dark page (`passesDualContrast`).\n */\nexport function providePalette(config: EmailPaletteConfig): Provider {\n return { provide: EMAIL_PALETTE, useFactory: () => resolvePalette(config) };\n}\n\n/** The palette in use, in an injection context. */\nexport function injectPalette(): EmailPalette {\n return inject(EMAIL_PALETTE);\n}\n\n/** The word a colour's name becomes in an id: lowercased, spaces as hyphens. */\nconst slug = (name: string): string => name.trim().toLowerCase().split(/\\s+/).join('-');\n\n/**\n * The palette as rows of a `/` menu, the way a chat's slash menu lists\n * colours: \"Red text\", \"Red background\" — one row per colour of either\n * side, in the section `color`, each carrying its `swatch`. Picked, a row\n * colours the selection, or what is typed next at a bare caret (the\n * `TextStyle` mark's `setColor` / `setBackgroundColor`); nothing, when the\n * kit has no `TextStyle`.\n *\n * items: (ctx) => [...extensionSuggestions(ctx), ...colorSuggestions(ctx, palette)]\n */\nexport function colorSuggestions(\n ctx: ExtensionContext,\n palette: EmailPalette,\n): SuggestionCommandItem[] {\n const textStyle = ctx.extensions.find((extension) => extension.name === 'textStyle');\n const commands = textStyle?.commands?.(ctx);\n const setColor = commands?.['setColor'];\n const setBackground = commands?.['setBackgroundColor'];\n if (!setColor || !setBackground) return [];\n const rows = (\n colors: readonly PaletteColor[],\n kind: 'color' | 'background',\n run: (value: string) => SuggestionCommandItem['command'],\n ): SuggestionCommandItem[] =>\n colors.map((color) => ({\n id: `${kind}-${slug(color.name)}`,\n title: kind === 'color' ? `${color.name} text` : `${color.name} background`,\n keywords: ['color', 'colour', kind === 'color' ? 'text' : 'highlight', color.name],\n section: 'color',\n swatch: color.value,\n command: run(color.value),\n }));\n return [\n ...rows(palette.text, 'color', (value) => setColor(value)),\n ...rows(palette.background, 'background', (value) => setBackground(value)),\n ];\n}\n","/*\n * Public API Surface of @h-k-dev/angular-email-editor/palette\n */\n\nexport * from './palette';\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":[],"mappings":";;;AAeA;AACkE;AAC3D,MAAM,YAAY,GAAiB;AACxC,IAAA,IAAI,EAAE,gBAAgB;AACtB,IAAA,UAAU,EAAE,sBAAsB;;AASpC;;;;AAIG;MACU,aAAa,GAAG,IAAI,cAAc,CAAe,eAAe,EAAE;AAC7E,IAAA,UAAU,EAAE,MAAM;AAClB,IAAA,OAAO,EAAE,MAAM,YAAY;AAC5B,CAAA;AAED;AACM,SAAU,cAAc,CAAC,MAA0B,EAAA;AACvD,IAAA,MAAM,KAAK,GAAG,OAAO,MAAM,KAAK,UAAU,GAAG,MAAM,CAAC,YAAY,CAAC,GAAG,MAAM;IAC1E,OAAO;AACL,QAAA,IAAI,EAAE,KAAK,CAAC,IAAI,IAAI,YAAY,CAAC,IAAI;AACrC,QAAA,UAAU,EAAE,KAAK,CAAC,UAAU,IAAI,YAAY,CAAC,UAAU;KACxD;AACH;AAEA;;;;;;;;;;AAUG;AACG,SAAU,cAAc,CAAC,MAA0B,EAAA;AACvD,IAAA,OAAO,EAAE,OAAO,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC,MAAM,CAAC,EAAE;AAC7E;AAEA;SACgB,aAAa,GAAA;AAC3B,IAAA,OAAO,MAAM,CAAC,aAAa,CAAC;AAC9B;AAEA;AACA,MAAM,IAAI,GAAG,CAAC,IAAY,KAAa,IAAI,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC;AAEvF;;;;;;;;;AASG;AACG,SAAU,gBAAgB,CAC9B,GAAqB,EACrB,OAAqB,EAAA;AAErB,IAAA,MAAM,SAAS,GAAG,GAAG,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,SAAS,KAAK,SAAS,CAAC,IAAI,KAAK,WAAW,CAAC;IACpF,MAAM,QAAQ,GAAG,SAAS,EAAE,QAAQ,GAAG,GAAG,CAAC;AAC3C,IAAA,MAAM,QAAQ,GAAG,QAAQ,GAAG,UAAU,CAAC;AACvC,IAAA,MAAM,aAAa,GAAG,QAAQ,GAAG,oBAAoB,CAAC;AACtD,IAAA,IAAI,CAAC,QAAQ,IAAI,CAAC,aAAa;AAAE,QAAA,OAAO,EAAE;IAC1C,MAAM,IAAI,GAAG,CACX,MAA+B,EAC/B,IAA4B,EAC5B,GAAwD,KAExD,MAAM,CAAC,GAAG,CAAC,CAAC,KAAK,MAAM;QACrB,EAAE,EAAE,CAAA,EAAG,IAAI,CAAA,CAAA,EAAI,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAA,CAAE;AACjC,QAAA,KAAK,EAAE,IAAI,KAAK,OAAO,GAAG,CAAA,EAAG,KAAK,CAAC,IAAI,CAAA,KAAA,CAAO,GAAG,GAAG,KAAK,CAAC,IAAI,CAAA,WAAA,CAAa;QAC3E,QAAQ,EAAE,CAAC,OAAO,EAAE,QAAQ,EAAE,IAAI,KAAK,OAAO,GAAG,MAAM,GAAG,WAAW,EAAE,KAAK,CAAC,IAAI,CAAC;AAClF,QAAA,OAAO,EAAE,OAAO;QAChB,MAAM,EAAE,KAAK,CAAC,KAAK;AACnB,QAAA,OAAO,EAAE,GAAG,CAAC,KAAK,CAAC,KAAK,CAAC;AAC1B,KAAA,CAAC,CAAC;IACL,OAAO;AACL,QAAA,GAAG,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,OAAO,EAAE,CAAC,KAAK,KAAK,QAAQ,CAAC,KAAK,CAAC,CAAC;AAC1D,QAAA,GAAG,IAAI,CAAC,OAAO,CAAC,UAAU,EAAE,YAAY,EAAE,CAAC,KAAK,KAAK,aAAa,CAAC,KAAK,CAAC,CAAC;KAC3E;AACH;;AC1GA;;AAEG;;ACFH;;AAEG;;;;"}
@@ -0,0 +1,307 @@
1
+ import * as i0 from '@angular/core';
2
+ import { inject, Injector, computed, input, Directive, output, DOCUMENT, effect } from '@angular/core';
3
+ import { proposalRange, isStreaming, discardProposal, acceptProposal, stopProposal, proposeContent, reviseProposal } from '@h-k-dev/angular-email-editor';
4
+ import { editorState } from '@h-k-dev/angular-email-editor/actions';
5
+
6
+ /**
7
+ * The proposal of an editor, bound and live — for a host's own panel: an
8
+ * input of its choosing to ask with, and buttons to accept or discard:
9
+ *
10
+ * readonly proposal = injectProposal(() => this.editor());
11
+ *
12
+ * this.proposal.propose(async ({ write, signal }) => {
13
+ * for await (const piece of model.write(prompt, { signal })) write(piece);
14
+ * }, { format: 'html' });
15
+ *
16
+ * <button [emailProposalAccept]="proposal">Apply</button>
17
+ * <button [emailProposalDiscard]="proposal">Discard</button>
18
+ *
19
+ * `editor` is read reactively: the proposal follows the editor it is given.
20
+ * Call in an injection context, or hand it an injector.
21
+ */
22
+ function injectProposal(editor, options = {}) {
23
+ const injector = options.injector ?? inject(Injector);
24
+ const state = editorState(editor, { injector });
25
+ const range = computed(() => {
26
+ const now = state();
27
+ return now ? proposalRange(now) : null;
28
+ }, /* @ts-ignore */
29
+ ...(ngDevMode ? [{ debugName: "range" }] : /* istanbul ignore next */ []));
30
+ const streaming = computed(() => {
31
+ const now = state();
32
+ return !!now && !!proposalRange(now) && isStreaming(now);
33
+ }, /* @ts-ignore */
34
+ ...(ngDevMode ? [{ debugName: "streaming" }] : /* istanbul ignore next */ []));
35
+ const box = computed(() => {
36
+ const now = state();
37
+ const current = editor();
38
+ if (!now || !current || current.view.isDestroyed)
39
+ return null;
40
+ const proposed = proposalRange(now);
41
+ const at = proposed ? proposed.to : now.selection.from;
42
+ // The line the proposal ends on: from where the proposal starts on it —
43
+ // or, when the proposal began on an earlier line, from the block's own
44
+ // left edge — to its end.
45
+ const pos = Math.min(at, now.doc.content.size);
46
+ const end = current.view.coordsAtPos(pos, -1);
47
+ const start = current.view.coordsAtPos(proposed ? proposed.from : pos);
48
+ let left = Math.min(start.left, end.left);
49
+ if (Math.abs(start.top - end.top) >= 1) {
50
+ const $end = now.doc.resolve(pos);
51
+ const block = $end.depth ? current.view.nodeDOM($end.before($end.depth)) : null;
52
+ left = block instanceof HTMLElement ? block.getBoundingClientRect().left : end.left;
53
+ }
54
+ return {
55
+ left,
56
+ top: end.top,
57
+ width: Math.max(0, end.right - left),
58
+ height: end.bottom - end.top,
59
+ };
60
+ }, /* @ts-ignore */
61
+ ...(ngDevMode ? [{ debugName: "box" }] : /* istanbul ignore next */ []));
62
+ const selectedPart = computed(() => {
63
+ const now = state();
64
+ const proposed = now && proposalRange(now);
65
+ if (!now || !proposed)
66
+ return null;
67
+ const { from, to, empty } = now.selection;
68
+ return !empty && from >= proposed.from && to <= proposed.to ? { from, to } : null;
69
+ }, /* @ts-ignore */
70
+ ...(ngDevMode ? [{ debugName: "selectedPart" }] : /* istanbul ignore next */ []));
71
+ return {
72
+ active: computed(() => range() !== null),
73
+ selectedPart,
74
+ revise: (callback, options = {}) => {
75
+ const current = editor();
76
+ const { range: given, ...rest } = options;
77
+ const part = given ?? selectedPart();
78
+ if (!current || !part)
79
+ return null;
80
+ return reviseProposal(current.view, part, callback, rest);
81
+ },
82
+ streaming,
83
+ thinking: computed(() => {
84
+ const current = range();
85
+ return streaming() && !!current && current.to === current.from;
86
+ }),
87
+ range,
88
+ box,
89
+ propose: (callback, options = {}) => {
90
+ const current = editor();
91
+ if (!current)
92
+ return null;
93
+ const { target, ...rest } = options;
94
+ return proposeContent(current.view, target ?? current.state.selection.from, callback, rest);
95
+ },
96
+ stop: () => {
97
+ const current = editor();
98
+ if (current)
99
+ stopProposal(current.view);
100
+ },
101
+ accept: () => {
102
+ const current = editor();
103
+ return !!current && acceptProposal(current.view);
104
+ },
105
+ discard: () => {
106
+ const current = editor();
107
+ return !!current && discardProposal(current.view);
108
+ },
109
+ };
110
+ }
111
+ /**
112
+ * The proposal of an editor, as a directive: put it on the element that
113
+ * holds the host's panel — the chat, the buttons — and everything inside
114
+ * has it, by injection, with nothing to bind:
115
+ *
116
+ * <div [emailProposal]="editor" #p="emailProposal">
117
+ * <button emailProposalAccept [disabled]="!p.active()">Apply</button>
118
+ * <button emailProposalDiscard>Discard</button>
119
+ * </div>
120
+ *
121
+ * The directive *is* the {@link EditorProposal}: `p.active()`,
122
+ * `p.propose(…)`, `p.accept()` — the same face `injectProposal` gives a
123
+ * class, for a host that lives in its template. The two are one thing
124
+ * looked at from two sides; a trigger takes either, bound or injected.
125
+ */
126
+ class Proposal {
127
+ /** The editor the proposal is in — read reactively, so it may come
128
+ later, or change. */
129
+ editor = input.required({ ...(ngDevMode ? { debugName: "editor" } : /* istanbul ignore next */ {}), alias: 'emailProposal' });
130
+ #proposal = injectProposal(() => this.editor());
131
+ active = this.#proposal.active;
132
+ streaming = this.#proposal.streaming;
133
+ thinking = this.#proposal.thinking;
134
+ range = this.#proposal.range;
135
+ box = this.#proposal.box;
136
+ selectedPart = this.#proposal.selectedPart;
137
+ propose(...args) {
138
+ return this.#proposal.propose(...args);
139
+ }
140
+ revise(...args) {
141
+ return this.#proposal.revise(...args);
142
+ }
143
+ stop() {
144
+ this.#proposal.stop();
145
+ }
146
+ accept() {
147
+ return this.#proposal.accept();
148
+ }
149
+ discard() {
150
+ return this.#proposal.discard();
151
+ }
152
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: Proposal, deps: [], target: i0.ɵɵFactoryTarget.Directive });
153
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "22.1.6", type: Proposal, isStandalone: true, selector: "[emailProposal]", inputs: { editor: { classPropertyName: "editor", publicName: "emailProposal", isSignal: true, isRequired: true, transformFunction: null } }, exportAs: ["emailProposal"], ngImport: i0 });
154
+ }
155
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: Proposal, decorators: [{
156
+ type: Directive,
157
+ args: [{ selector: '[emailProposal]', exportAs: 'emailProposal' }]
158
+ }], propDecorators: { editor: [{ type: i0.Input, args: [{ isSignal: true, alias: "emailProposal", required: true }] }] } });
159
+ /** A trigger's own binding: an `EditorProposal`, or nothing — the bare
160
+ attribute (`''`) counts as nothing, and the ancestor's is used. */
161
+ const bound = (value) => value || undefined;
162
+ /** The proposal a trigger acts on: the one bound to it, else the one an
163
+ `[emailProposal]` ancestor provides. Neither is a mistake, and said so. */
164
+ function resolveProposal(own, selector) {
165
+ const provided = inject(Proposal, { optional: true });
166
+ return computed(() => {
167
+ const proposal = own() ?? provided ?? undefined;
168
+ if (!proposal) {
169
+ throw new Error(`[${selector}] has no proposal: bind one ([${selector}]="proposal"), ` +
170
+ `or put [emailProposal] on an ancestor.`);
171
+ }
172
+ return proposal;
173
+ });
174
+ }
175
+ /**
176
+ * Makes the host's own button the one that accepts the proposal: a click
177
+ * takes it into the document and hands the caret back; disabled — by the
178
+ * host's own means, bound from `disabled()` — while nothing is proposed or
179
+ * it is still being written. The proposal is bound, or, bare, the
180
+ * `[emailProposal]` ancestor's.
181
+ *
182
+ * <button mat-button [emailProposalAccept]="proposal" #apply="emailProposalAccept"
183
+ * [disabled]="apply.disabled()">Apply</button>
184
+ */
185
+ class ProposalAccept {
186
+ proposal = input(undefined, { ...(ngDevMode ? { debugName: "proposal" } : /* istanbul ignore next */ {}), alias: 'emailProposalAccept', transform: bound });
187
+ #proposal = resolveProposal(this.proposal, 'emailProposalAccept');
188
+ disabled = computed(() => !this.#proposal().active() || this.#proposal().streaming(), /* @ts-ignore */
189
+ ...(ngDevMode ? [{ debugName: "disabled" }] : /* istanbul ignore next */ []));
190
+ run() {
191
+ if (this.disabled())
192
+ return false;
193
+ return this.#proposal().accept();
194
+ }
195
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: ProposalAccept, deps: [], target: i0.ɵɵFactoryTarget.Directive });
196
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "22.1.6", type: ProposalAccept, isStandalone: true, selector: "[emailProposalAccept]", inputs: { proposal: { classPropertyName: "proposal", publicName: "emailProposalAccept", isSignal: true, isRequired: false, transformFunction: null } }, host: { listeners: { "click": "run()" }, properties: { "attr.aria-disabled": "disabled() ? true : null" } }, exportAs: ["emailProposalAccept"], ngImport: i0 });
197
+ }
198
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: ProposalAccept, decorators: [{
199
+ type: Directive,
200
+ args: [{
201
+ selector: '[emailProposalAccept]',
202
+ exportAs: 'emailProposalAccept',
203
+ host: { '(click)': 'run()', '[attr.aria-disabled]': 'disabled() ? true : null' },
204
+ }]
205
+ }], propDecorators: { proposal: [{ type: i0.Input, args: [{ isSignal: true, alias: "emailProposalAccept", required: false }] }] } });
206
+ /**
207
+ * Makes the host's own button the one that discards the proposal: a click
208
+ * takes it out, wherever the writing has got to. The proposal is bound,
209
+ * or, bare, the `[emailProposal]` ancestor's.
210
+ *
211
+ * <button mat-button [emailProposalDiscard]="proposal">Discard</button>
212
+ */
213
+ class ProposalDiscard {
214
+ proposal = input(undefined, { ...(ngDevMode ? { debugName: "proposal" } : /* istanbul ignore next */ {}), alias: 'emailProposalDiscard', transform: bound });
215
+ #proposal = resolveProposal(this.proposal, 'emailProposalDiscard');
216
+ disabled = computed(() => !this.#proposal().active(), /* @ts-ignore */
217
+ ...(ngDevMode ? [{ debugName: "disabled" }] : /* istanbul ignore next */ []));
218
+ run() {
219
+ if (this.disabled())
220
+ return false;
221
+ return this.#proposal().discard();
222
+ }
223
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: ProposalDiscard, deps: [], target: i0.ɵɵFactoryTarget.Directive });
224
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "22.1.6", type: ProposalDiscard, isStandalone: true, selector: "[emailProposalDiscard]", inputs: { proposal: { classPropertyName: "proposal", publicName: "emailProposalDiscard", isSignal: true, isRequired: false, transformFunction: null } }, host: { listeners: { "click": "run()" }, properties: { "attr.aria-disabled": "disabled() ? true : null" } }, exportAs: ["emailProposalDiscard"], ngImport: i0 });
225
+ }
226
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: ProposalDiscard, decorators: [{
227
+ type: Directive,
228
+ args: [{
229
+ selector: '[emailProposalDiscard]',
230
+ exportAs: 'emailProposalDiscard',
231
+ host: { '(click)': 'run()', '[attr.aria-disabled]': 'disabled() ? true : null' },
232
+ }]
233
+ }], propDecorators: { proposal: [{ type: i0.Input, args: [{ isSignal: true, alias: "emailProposalDiscard", required: false }] }] } });
234
+ /**
235
+ * The keys of a proposal, heard from anywhere on the page — the text, the
236
+ * host's input, a button — while one stands, before anything else hears
237
+ * them (the document, capture phase):
238
+ *
239
+ * - **Ctrl-Enter** (⌘-Enter on a Mac) accepts: a proposal on the table is
240
+ * what the key commits, and whatever the editor binds to it (a send,
241
+ * say) never sees it. While the proposal is still being written the key
242
+ * is swallowed — nothing is sent, nothing is applied; one thing at a
243
+ * time. `accepted` says it happened.
244
+ * - **Escape** is the host's: `escape` carries the event, and the host
245
+ * decides — close a menu over the text first, or let the proposal go —
246
+ * because what else is open is the host's to know. Nothing is prevented
247
+ * or stopped for it; the host does that on the event if it acts.
248
+ *
249
+ * Nothing is heard while nothing is proposed, and an IME's own keys stay
250
+ * the IME's. The proposal is bound, or, bare, the `[emailProposal]`
251
+ * ancestor's.
252
+ *
253
+ * <div [emailProposal]="editor" emailProposalKeys
254
+ * (accepted)="done()" (escape)="$event.preventDefault(); leave()">
255
+ */
256
+ class ProposalKeys {
257
+ proposal = input(undefined, { ...(ngDevMode ? { debugName: "proposal" } : /* istanbul ignore next */ {}), alias: 'emailProposalKeys', transform: bound });
258
+ /** Ctrl-Enter (⌘-Enter) took the proposal into the document. */
259
+ accepted = output();
260
+ /** Escape, with a proposal standing — for the host to act on. */
261
+ escape = output();
262
+ #proposal = resolveProposal(this.proposal, 'emailProposalKeys');
263
+ #document = inject(DOCUMENT);
264
+ constructor() {
265
+ effect((onCleanup) => {
266
+ const proposal = this.#proposal();
267
+ if (!proposal.active())
268
+ return;
269
+ const onKeydown = (event) => {
270
+ if (event.isComposing)
271
+ return;
272
+ if (event.key === 'Escape') {
273
+ this.escape.emit(event);
274
+ }
275
+ else if (event.key === 'Enter' && (isMac() ? event.metaKey : event.ctrlKey)) {
276
+ event.preventDefault();
277
+ event.stopPropagation();
278
+ if (!proposal.streaming() && proposal.accept())
279
+ this.accepted.emit();
280
+ }
281
+ };
282
+ this.#document.addEventListener('keydown', onKeydown, true);
283
+ onCleanup(() => this.#document.removeEventListener('keydown', onKeydown, true));
284
+ });
285
+ }
286
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: ProposalKeys, deps: [], target: i0.ɵɵFactoryTarget.Directive });
287
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "22.1.6", type: ProposalKeys, isStandalone: true, selector: "[emailProposalKeys]", inputs: { proposal: { classPropertyName: "proposal", publicName: "emailProposalKeys", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { accepted: "accepted", escape: "escape" }, exportAs: ["emailProposalKeys"], ngImport: i0 });
288
+ }
289
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: ProposalKeys, decorators: [{
290
+ type: Directive,
291
+ args: [{ selector: '[emailProposalKeys]', exportAs: 'emailProposalKeys' }]
292
+ }], ctorParameters: () => [], propDecorators: { proposal: [{ type: i0.Input, args: [{ isSignal: true, alias: "emailProposalKeys", required: false }] }], accepted: [{ type: i0.Output, args: ["accepted"] }], escape: [{ type: i0.Output, args: ["escape"] }] } });
293
+ /** Whether the keyboard is a Mac's: ⌘ where others hold Ctrl. */
294
+ function isMac() {
295
+ return typeof navigator !== 'undefined' && /Mac|iP(hone|ad|od)/.test(navigator.platform);
296
+ }
297
+
298
+ /*
299
+ * Public API Surface of @h-k-dev/angular-email-editor/proposal
300
+ */
301
+
302
+ /**
303
+ * Generated bundle index. Do not edit.
304
+ */
305
+
306
+ export { Proposal, ProposalAccept, ProposalDiscard, ProposalKeys, injectProposal };
307
+ //# sourceMappingURL=h-k-dev-angular-email-editor-proposal.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"h-k-dev-angular-email-editor-proposal.mjs","sources":["../../../projects/angular-email-editor/proposal/proposal.ts","../../../projects/angular-email-editor/proposal/public-api.ts","../../../projects/angular-email-editor/proposal/h-k-dev-angular-email-editor-proposal.ts"],"sourcesContent":["import {\n DOCUMENT,\n Directive,\n Injector,\n Signal,\n computed,\n effect,\n inject,\n input,\n output,\n} from '@angular/core';\nimport {\n ContentStreamRange,\n ContentStreamRun,\n ContentStreamWriter,\n Editor,\n StreamContentOptions,\n acceptProposal,\n discardProposal,\n isStreaming,\n proposalRange,\n proposeContent,\n reviseProposal,\n stopProposal,\n} from '@h-k-dev/angular-email-editor';\nimport { editorState } from '@h-k-dev/angular-email-editor/actions';\nimport { AnchorRect } from '@h-k-dev/angular-email-editor/anchor';\n\n/**\n * A proposal in an editor, as a host's panel sees it: what is proposed and\n * whether it is still coming, where it stands, and the three things to do\n * about it. The reactive face of `proposeContent` / `acceptProposal` /\n * `discardProposal` (the main entry, which needs `createContentProposal()`\n * and `createContentStream()` in the kit).\n */\nexport interface EditorProposal {\n /** Something is proposed — written into the text, not the writer's yet. */\n readonly active: Signal<boolean>;\n /** The proposal is still being written. */\n readonly streaming: Signal<boolean>;\n /** Asked, and nothing has come yet: the model is thinking — for a host\n to show it, since there is nothing in the text to see. */\n readonly thinking: Signal<boolean>;\n /** Where it stands, in the document. */\n readonly range: Signal<ContentStreamRange | null>;\n /** Its box in viewport coordinates — the last line's, for a panel to\n stand under; a caret's when nothing is proposed yet. Measured anew on\n every change of the editor, so it follows the writing. */\n readonly box: Signal<AnchorRect | null>;\n /** Proposes content at the selection (or at `target`), written by\n `callback` — see `streamContent` for the writer and the options. A\n proposal already there is discarded first. `null` without an editor. */\n propose(\n callback: (writer: ContentStreamWriter) => Promise<void> | void,\n options?: Omit<StreamContentOptions, 'history'> & { target?: number | ContentStreamRange },\n ): ContentStreamRun | null;\n /** Writes a part of the proposal again — `range`, inside it, or the\n editor's selection when it lies inside — the rest standing. `null`\n when there is no proposal holding it. */\n revise(\n callback: (writer: ContentStreamWriter) => Promise<void> | void,\n options?: Omit<StreamContentOptions, 'history'> & { range?: ContentStreamRange },\n ): ContentStreamRun | null;\n /** The editor's selection, when it is a range inside the proposal —\n what {@link revise} would write again; null otherwise. */\n readonly selectedPart: Signal<ContentStreamRange | null>;\n /** Stops the writing where it is; what is proposed stays proposed. */\n stop(): void;\n /** Takes the proposal into the document: one change, one undo. */\n accept(): boolean;\n /** Takes it out, as if never written. */\n discard(): boolean;\n}\n\n/**\n * The proposal of an editor, bound and live — for a host's own panel: an\n * input of its choosing to ask with, and buttons to accept or discard:\n *\n * readonly proposal = injectProposal(() => this.editor());\n *\n * this.proposal.propose(async ({ write, signal }) => {\n * for await (const piece of model.write(prompt, { signal })) write(piece);\n * }, { format: 'html' });\n *\n * <button [emailProposalAccept]=\"proposal\">Apply</button>\n * <button [emailProposalDiscard]=\"proposal\">Discard</button>\n *\n * `editor` is read reactively: the proposal follows the editor it is given.\n * Call in an injection context, or hand it an injector.\n */\nexport function injectProposal(\n editor: () => Editor | undefined,\n options: { injector?: Injector } = {},\n): EditorProposal {\n const injector = options.injector ?? inject(Injector);\n const state = editorState(editor, { injector });\n const range = computed(() => {\n const now = state();\n return now ? proposalRange(now) : null;\n });\n const streaming = computed(() => {\n const now = state();\n return !!now && !!proposalRange(now) && isStreaming(now);\n });\n const box = computed<AnchorRect | null>(() => {\n const now = state();\n const current = editor();\n if (!now || !current || current.view.isDestroyed) return null;\n const proposed = proposalRange(now);\n const at = proposed ? proposed.to : now.selection.from;\n // The line the proposal ends on: from where the proposal starts on it —\n // or, when the proposal began on an earlier line, from the block's own\n // left edge — to its end.\n const pos = Math.min(at, now.doc.content.size);\n const end = current.view.coordsAtPos(pos, -1);\n const start = current.view.coordsAtPos(proposed ? proposed.from : pos);\n let left = Math.min(start.left, end.left);\n if (Math.abs(start.top - end.top) >= 1) {\n const $end = now.doc.resolve(pos);\n const block = $end.depth ? current.view.nodeDOM($end.before($end.depth)) : null;\n left = block instanceof HTMLElement ? block.getBoundingClientRect().left : end.left;\n }\n return {\n left,\n top: end.top,\n width: Math.max(0, end.right - left),\n height: end.bottom - end.top,\n };\n });\n const selectedPart = computed<ContentStreamRange | null>(() => {\n const now = state();\n const proposed = now && proposalRange(now);\n if (!now || !proposed) return null;\n const { from, to, empty } = now.selection;\n return !empty && from >= proposed.from && to <= proposed.to ? { from, to } : null;\n });\n return {\n active: computed(() => range() !== null),\n selectedPart,\n revise: (callback, options = {}) => {\n const current = editor();\n const { range: given, ...rest } = options;\n const part = given ?? selectedPart();\n if (!current || !part) return null;\n return reviseProposal(current.view, part, callback, rest);\n },\n streaming,\n thinking: computed(() => {\n const current = range();\n return streaming() && !!current && current.to === current.from;\n }),\n range,\n box,\n propose: (callback, options = {}) => {\n const current = editor();\n if (!current) return null;\n const { target, ...rest } = options;\n return proposeContent(current.view, target ?? current.state.selection.from, callback, rest);\n },\n stop: () => {\n const current = editor();\n if (current) stopProposal(current.view);\n },\n accept: () => {\n const current = editor();\n return !!current && acceptProposal(current.view);\n },\n discard: () => {\n const current = editor();\n return !!current && discardProposal(current.view);\n },\n };\n}\n\n/**\n * The proposal of an editor, as a directive: put it on the element that\n * holds the host's panel — the chat, the buttons — and everything inside\n * has it, by injection, with nothing to bind:\n *\n * <div [emailProposal]=\"editor\" #p=\"emailProposal\">\n * <button emailProposalAccept [disabled]=\"!p.active()\">Apply</button>\n * <button emailProposalDiscard>Discard</button>\n * </div>\n *\n * The directive *is* the {@link EditorProposal}: `p.active()`,\n * `p.propose(…)`, `p.accept()` — the same face `injectProposal` gives a\n * class, for a host that lives in its template. The two are one thing\n * looked at from two sides; a trigger takes either, bound or injected.\n */\n@Directive({ selector: '[emailProposal]', exportAs: 'emailProposal' })\nexport class Proposal implements EditorProposal {\n /** The editor the proposal is in — read reactively, so it may come\n later, or change. */\n readonly editor = input.required<Editor | undefined>({ alias: 'emailProposal' });\n\n readonly #proposal = injectProposal(() => this.editor());\n\n readonly active = this.#proposal.active;\n readonly streaming = this.#proposal.streaming;\n readonly thinking = this.#proposal.thinking;\n readonly range = this.#proposal.range;\n readonly box = this.#proposal.box;\n readonly selectedPart = this.#proposal.selectedPart;\n\n propose(...args: Parameters<EditorProposal['propose']>): ContentStreamRun | null {\n return this.#proposal.propose(...args);\n }\n\n revise(...args: Parameters<EditorProposal['revise']>): ContentStreamRun | null {\n return this.#proposal.revise(...args);\n }\n\n stop(): void {\n this.#proposal.stop();\n }\n\n accept(): boolean {\n return this.#proposal.accept();\n }\n\n discard(): boolean {\n return this.#proposal.discard();\n }\n}\n\n/** A trigger's own binding: an `EditorProposal`, or nothing — the bare\n attribute (`''`) counts as nothing, and the ancestor's is used. */\nconst bound = (value: EditorProposal | '' | undefined): EditorProposal | undefined =>\n value || undefined;\n\n/** The proposal a trigger acts on: the one bound to it, else the one an\n `[emailProposal]` ancestor provides. Neither is a mistake, and said so. */\nfunction resolveProposal(\n own: Signal<EditorProposal | undefined>,\n selector: string,\n): Signal<EditorProposal> {\n const provided = inject(Proposal, { optional: true });\n return computed(() => {\n const proposal = own() ?? provided ?? undefined;\n if (!proposal) {\n throw new Error(\n `[${selector}] has no proposal: bind one ([${selector}]=\"proposal\"), ` +\n `or put [emailProposal] on an ancestor.`,\n );\n }\n return proposal;\n });\n}\n\n/**\n * Makes the host's own button the one that accepts the proposal: a click\n * takes it into the document and hands the caret back; disabled — by the\n * host's own means, bound from `disabled()` — while nothing is proposed or\n * it is still being written. The proposal is bound, or, bare, the\n * `[emailProposal]` ancestor's.\n *\n * <button mat-button [emailProposalAccept]=\"proposal\" #apply=\"emailProposalAccept\"\n * [disabled]=\"apply.disabled()\">Apply</button>\n */\n@Directive({\n selector: '[emailProposalAccept]',\n exportAs: 'emailProposalAccept',\n host: { '(click)': 'run()', '[attr.aria-disabled]': 'disabled() ? true : null' },\n})\nexport class ProposalAccept {\n readonly proposal = input(undefined, { alias: 'emailProposalAccept', transform: bound });\n\n readonly #proposal = resolveProposal(this.proposal, 'emailProposalAccept');\n\n readonly disabled = computed(() => !this.#proposal().active() || this.#proposal().streaming());\n\n run(): boolean {\n if (this.disabled()) return false;\n return this.#proposal().accept();\n }\n}\n\n/**\n * Makes the host's own button the one that discards the proposal: a click\n * takes it out, wherever the writing has got to. The proposal is bound,\n * or, bare, the `[emailProposal]` ancestor's.\n *\n * <button mat-button [emailProposalDiscard]=\"proposal\">Discard</button>\n */\n@Directive({\n selector: '[emailProposalDiscard]',\n exportAs: 'emailProposalDiscard',\n host: { '(click)': 'run()', '[attr.aria-disabled]': 'disabled() ? true : null' },\n})\nexport class ProposalDiscard {\n readonly proposal = input(undefined, { alias: 'emailProposalDiscard', transform: bound });\n\n readonly #proposal = resolveProposal(this.proposal, 'emailProposalDiscard');\n\n readonly disabled = computed(() => !this.#proposal().active());\n\n run(): boolean {\n if (this.disabled()) return false;\n return this.#proposal().discard();\n }\n}\n\n/**\n * The keys of a proposal, heard from anywhere on the page — the text, the\n * host's input, a button — while one stands, before anything else hears\n * them (the document, capture phase):\n *\n * - **Ctrl-Enter** (⌘-Enter on a Mac) accepts: a proposal on the table is\n * what the key commits, and whatever the editor binds to it (a send,\n * say) never sees it. While the proposal is still being written the key\n * is swallowed — nothing is sent, nothing is applied; one thing at a\n * time. `accepted` says it happened.\n * - **Escape** is the host's: `escape` carries the event, and the host\n * decides — close a menu over the text first, or let the proposal go —\n * because what else is open is the host's to know. Nothing is prevented\n * or stopped for it; the host does that on the event if it acts.\n *\n * Nothing is heard while nothing is proposed, and an IME's own keys stay\n * the IME's. The proposal is bound, or, bare, the `[emailProposal]`\n * ancestor's.\n *\n * <div [emailProposal]=\"editor\" emailProposalKeys\n * (accepted)=\"done()\" (escape)=\"$event.preventDefault(); leave()\">\n */\n@Directive({ selector: '[emailProposalKeys]', exportAs: 'emailProposalKeys' })\nexport class ProposalKeys {\n readonly proposal = input(undefined, { alias: 'emailProposalKeys', transform: bound });\n\n /** Ctrl-Enter (⌘-Enter) took the proposal into the document. */\n readonly accepted = output<void>();\n\n /** Escape, with a proposal standing — for the host to act on. */\n readonly escape = output<KeyboardEvent>();\n\n readonly #proposal = resolveProposal(this.proposal, 'emailProposalKeys');\n\n readonly #document = inject(DOCUMENT);\n\n constructor() {\n effect((onCleanup) => {\n const proposal = this.#proposal();\n if (!proposal.active()) return;\n const onKeydown = (event: KeyboardEvent) => {\n if (event.isComposing) return;\n if (event.key === 'Escape') {\n this.escape.emit(event);\n } else if (event.key === 'Enter' && (isMac() ? event.metaKey : event.ctrlKey)) {\n event.preventDefault();\n event.stopPropagation();\n if (!proposal.streaming() && proposal.accept()) this.accepted.emit();\n }\n };\n this.#document.addEventListener('keydown', onKeydown, true);\n onCleanup(() => this.#document.removeEventListener('keydown', onKeydown, true));\n });\n }\n}\n\n/** Whether the keyboard is a Mac's: ⌘ where others hold Ctrl. */\nfunction isMac(): boolean {\n return typeof navigator !== 'undefined' && /Mac|iP(hone|ad|od)/.test(navigator.platform);\n}\n","/*\n * Public API Surface of @h-k-dev/angular-email-editor/proposal\n */\n\nexport * from './proposal';\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":[],"mappings":"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"}