@h-k-dev/angular-email-editor 0.0.0-stage → 0.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +127 -2
- 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 +383 -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 +1106 -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 +78 -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 +18820 -0
- package/fesm2022/h-k-dev-angular-email-editor.mjs.map +1 -0
- package/package.json +133 -3
- 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 +234 -0
- package/types/h-k-dev-angular-email-editor-address-input.d.ts +449 -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 +46 -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,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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"}
|