@h-k-dev/angular-email-editor 0.0.3 → 0.0.5

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.
@@ -0,0 +1,144 @@
1
+ /**
2
+ * The fonts an email is written in — Gmail's set, in Gmail's order — as
3
+ * plain data: no Angular, no ProseMirror. The editor's `textStyle` mark
4
+ * stores and emits only these stacks, the toolbar offers them, and an
5
+ * editor of another kind (summernote's `fontNames`) can offer the same set
6
+ * from the same constant, so a message reads the same font whichever
7
+ * composer wrote it.
8
+ */
9
+ /** The clients, in the order a picker shows them (G, A, O), with the letter
10
+ and name it shows them by. */
11
+ const fontClients = [
12
+ { id: 'gmail', letter: 'G', name: 'Gmail' },
13
+ { id: 'apple-mail', letter: 'A', name: 'Apple Mail' },
14
+ { id: 'outlook-desktop', letter: 'O', name: 'Outlook' },
15
+ ];
16
+ /** The family names of a `font-family` value, unquoted, in order. */
17
+ function familiesOf(stack) {
18
+ return stack
19
+ .split(',')
20
+ .map((family) => family.replace(/["']/g, '').trim())
21
+ .filter(Boolean);
22
+ }
23
+ const G = 'gmail';
24
+ const A = 'apple-mail';
25
+ const O = 'outlook-desktop';
26
+ function font(id, name, stack, clients) {
27
+ return { id, name, stack, face: familiesOf(stack)[0], clients };
28
+ }
29
+ /**
30
+ * Gmail's fonts, in its menu's order. A stack is the face, then what a
31
+ * client without it should draw instead, then the generic. Each face leads
32
+ * only its own stack, so a single family read back from another editor
33
+ * (`font-family: 'Arial Black'`) finds its font exactly.
34
+ *
35
+ * The first stack naming a family claims it (see {@link matchEmailFont}):
36
+ * `helvetica` and `sans-serif` read as Sans Serif, `times` and `serif` as
37
+ * Serif, `courier` and `monospace` as Fixed width.
38
+ */
39
+ const emailFontFamilies = [
40
+ font('sans-serif', 'Sans Serif', 'Arial, Helvetica, sans-serif', [G, A, O]),
41
+ font('serif', 'Serif', '"Times New Roman", Times, serif', [G, A, O]),
42
+ font('fixed-width', 'Fixed width', '"Courier New", Courier, monospace', [G, A, O]),
43
+ // Not on iOS.
44
+ font('wide', 'Wide', '"Arial Black", sans-serif', [G, O]),
45
+ // Office's, not Windows' own — nor on iOS.
46
+ font('narrow', 'Narrow', '"Arial Narrow", sans-serif', [O]),
47
+ // Not on iOS.
48
+ font('comic-sans-ms', 'Comic Sans MS', '"Comic Sans MS", sans-serif', [G, O]),
49
+ // Office's only: neither macOS, iOS nor Windows itself has it.
50
+ font('garamond', 'Garamond', 'Garamond, "Times New Roman", serif', [O]),
51
+ font('georgia', 'Georgia', 'Georgia, serif', [G, A, O]),
52
+ // Not on iOS.
53
+ font('tahoma', 'Tahoma', 'Tahoma, sans-serif', [G, O]),
54
+ font('trebuchet-ms', 'Trebuchet MS', '"Trebuchet MS", sans-serif', [G, A, O]),
55
+ font('verdana', 'Verdana', 'Verdana, sans-serif', [G, A, O]),
56
+ ];
57
+ /**
58
+ * The font a message is written in until the writer picks another — Gmail's
59
+ * Sans Serif, Arial. Nothing is emitted for it: unstyled text is drawn in
60
+ * the client's own default, as Gmail's is. An editor shows it as its
61
+ * surface font, so what is written looks as it will in Gmail; summernote
62
+ * takes its face (`fontName: emailDefaultFont.face`).
63
+ */
64
+ const emailDefaultFont = emailFontFamilies[0];
65
+ /**
66
+ * The faces of {@link emailFontFamilies}, in order — the set for an editor
67
+ * that offers single families. For summernote, both lists: `fontNamesIgnoreCheck`
68
+ * keeps a face this machine lacks (Garamond, Arial Narrow) in the menu.
69
+ *
70
+ * ```ts
71
+ * $('#editor').summernote({ fontNames: emailFontFaces, fontNamesIgnoreCheck: emailFontFaces });
72
+ * ```
73
+ */
74
+ const emailFontFaces = emailFontFamilies.map((f) => f.face);
75
+ /** Every size the editor stores, in px. */
76
+ const allowedFontSizes = [10, 12, 14, 16, 18, 24, 32];
77
+ /**
78
+ * The sizes a picker offers: the phone-safe subset (≥14px). Below ~13px iOS
79
+ * auto-inflates text and reflows the layout, so they are not *offered* —
80
+ * but every {@link allowedFontSizes} size is still read and kept, because a
81
+ * hand-typed size in the HTML source is the author's own responsibility.
82
+ */
83
+ const emailFontSizes = [14, 16, 18, 24, 32];
84
+ /** A `font-family` value as a comparison key: lower-cased, quotes dropped,
85
+ whitespace around commas collapsed — so a hand-typed or CSSOM-reserialized
86
+ stack matches a curated one regardless of cosmetic differences. */
87
+ function fontFamilyKey(raw) {
88
+ return raw
89
+ .toLowerCase()
90
+ .replace(/["']/g, '')
91
+ .replace(/\s*,\s*/g, ',')
92
+ .trim();
93
+ }
94
+ const FONT_BY_KEY = new Map(emailFontFamilies.map((f) => [fontFamilyKey(f.stack), f]));
95
+ /** Each family a stack names to the first stack naming it. */
96
+ const FONT_BY_FAMILY = new Map();
97
+ for (const f of emailFontFamilies) {
98
+ for (const family of fontFamilyKey(f.stack).split(',')) {
99
+ if (!FONT_BY_FAMILY.has(family))
100
+ FONT_BY_FAMILY.set(family, f);
101
+ }
102
+ }
103
+ /** Whether `value` is exactly one of the stacks, give or take quotes, case
104
+ and spacing. */
105
+ function isEmailFontStack(value) {
106
+ return typeof value === 'string' && FONT_BY_KEY.has(fontFamilyKey(value));
107
+ }
108
+ /**
109
+ * The font a `font-family` value is. One of the stacks is itself; a stack of
110
+ * someone else's is the font it falls back to — the first of its families a
111
+ * stack names, its generic last: a builder's web font (`Ubuntu, Helvetica,
112
+ * Arial, sans-serif`) is Sans Serif, the face a client without that font
113
+ * draws anyway, and summernote's `'Comic Sans MS'` is Comic Sans MS.
114
+ * Nothing to fall back to, no font.
115
+ */
116
+ function matchEmailFont(raw) {
117
+ if (!raw)
118
+ return null;
119
+ const key = fontFamilyKey(raw);
120
+ const exact = FONT_BY_KEY.get(key);
121
+ if (exact)
122
+ return exact;
123
+ for (const family of key.split(',')) {
124
+ const found = FONT_BY_FAMILY.get(family.trim());
125
+ if (found)
126
+ return found;
127
+ }
128
+ return null;
129
+ }
130
+
131
+ /*
132
+ * Public API Surface of @h-k-dev/angular-email-editor/fonts
133
+ *
134
+ * Its own entry because it is plain data: a host whose other composer is
135
+ * not this editor (summernote) shares the font set without pulling in
136
+ * ProseMirror or Angular.
137
+ */
138
+
139
+ /**
140
+ * Generated bundle index. Do not edit.
141
+ */
142
+
143
+ export { allowedFontSizes, emailDefaultFont, emailFontFaces, emailFontFamilies, emailFontSizes, fontClients, fontFamilyKey, isEmailFontStack, matchEmailFont };
144
+ //# sourceMappingURL=h-k-dev-angular-email-editor-fonts.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"h-k-dev-angular-email-editor-fonts.mjs","sources":["../../../projects/angular-email-editor/fonts/fonts.ts","../../../projects/angular-email-editor/fonts/public-api.ts","../../../projects/angular-email-editor/fonts/h-k-dev-angular-email-editor-fonts.ts"],"sourcesContent":["/**\n * The fonts an email is written in — Gmail's set, in Gmail's order — as\n * plain data: no Angular, no ProseMirror. The editor's `textStyle` mark\n * stores and emits only these stacks, the toolbar offers them, and an\n * editor of another kind (summernote's `fontNames`) can offer the same set\n * from the same constant, so a message reads the same font whichever\n * composer wrote it.\n */\n\n/**\n * A client a font is checked against — the three a writer's recipients\n * mostly read in, the same ids as the library's `RenderingClient`.\n */\nexport type FontClient = 'gmail' | 'apple-mail' | 'outlook-desktop';\n\n/** The clients, in the order a picker shows them (G, A, O), with the letter\n and name it shows them by. */\nexport const fontClients: readonly { id: FontClient; letter: string; name: string }[] = [\n { id: 'gmail', letter: 'G', name: 'Gmail' },\n { id: 'apple-mail', letter: 'A', name: 'Apple Mail' },\n { id: 'outlook-desktop', letter: 'O', name: 'Outlook' },\n];\n\n/** A font the editor offers: one of {@link emailFontFamilies}. */\nexport interface EmailFont {\n /** Stable, for a host's translation key: `sans-serif`, `fixed-width`, … */\n readonly id: string;\n /** The label, in English — Gmail's words. */\n readonly name: string;\n /**\n * The exact `font-family` value emitted — the canonical stored form. A\n * family name of more than one word is double-quoted: the CSSOM (Chrome's\n * and jsdom's alike) quotes it on reserialization, so the stored bytes are\n * already what comes back and a round trip is stable.\n */\n readonly stack: string;\n /** The face the stack is for — its first family, unquoted: what an editor\n that sets a single family (summernote) offers for it. */\n readonly face: string;\n /**\n * The clients that draw the face itself rather than its fallback — the\n * face is on the machine the client runs on:\n *\n * - `gmail`: Gmail on the web, read on Windows or macOS (it draws with\n * the reader's own fonts; one of them lacking the face is a no).\n * - `apple-mail`: Apple Mail on macOS *and* iOS — a phone lacks Arial\n * Black, Comic Sans, Tahoma.\n * - `outlook-desktop`: Outlook on Windows, which brings Office's fonts\n * (Arial Narrow, Garamond).\n *\n * Elsewhere the stack's fallback draws: the email still reads, in a near\n * face. Curated, conservative — a face only some readers have is a no.\n */\n readonly clients: readonly FontClient[];\n}\n\n/** The family names of a `font-family` value, unquoted, in order. */\nfunction familiesOf(stack: string): string[] {\n return stack\n .split(',')\n .map((family) => family.replace(/[\"']/g, '').trim())\n .filter(Boolean);\n}\n\nconst G: FontClient = 'gmail';\nconst A: FontClient = 'apple-mail';\nconst O: FontClient = 'outlook-desktop';\n\nfunction font(id: string, name: string, stack: string, clients: FontClient[]): EmailFont {\n return { id, name, stack, face: familiesOf(stack)[0], clients };\n}\n\n/**\n * Gmail's fonts, in its menu's order. A stack is the face, then what a\n * client without it should draw instead, then the generic. Each face leads\n * only its own stack, so a single family read back from another editor\n * (`font-family: 'Arial Black'`) finds its font exactly.\n *\n * The first stack naming a family claims it (see {@link matchEmailFont}):\n * `helvetica` and `sans-serif` read as Sans Serif, `times` and `serif` as\n * Serif, `courier` and `monospace` as Fixed width.\n */\nexport const emailFontFamilies: readonly EmailFont[] = [\n font('sans-serif', 'Sans Serif', 'Arial, Helvetica, sans-serif', [G, A, O]),\n font('serif', 'Serif', '\"Times New Roman\", Times, serif', [G, A, O]),\n font('fixed-width', 'Fixed width', '\"Courier New\", Courier, monospace', [G, A, O]),\n // Not on iOS.\n font('wide', 'Wide', '\"Arial Black\", sans-serif', [G, O]),\n // Office's, not Windows' own — nor on iOS.\n font('narrow', 'Narrow', '\"Arial Narrow\", sans-serif', [O]),\n // Not on iOS.\n font('comic-sans-ms', 'Comic Sans MS', '\"Comic Sans MS\", sans-serif', [G, O]),\n // Office's only: neither macOS, iOS nor Windows itself has it.\n font('garamond', 'Garamond', 'Garamond, \"Times New Roman\", serif', [O]),\n font('georgia', 'Georgia', 'Georgia, serif', [G, A, O]),\n // Not on iOS.\n font('tahoma', 'Tahoma', 'Tahoma, sans-serif', [G, O]),\n font('trebuchet-ms', 'Trebuchet MS', '\"Trebuchet MS\", sans-serif', [G, A, O]),\n font('verdana', 'Verdana', 'Verdana, sans-serif', [G, A, O]),\n];\n\n/**\n * The font a message is written in until the writer picks another — Gmail's\n * Sans Serif, Arial. Nothing is emitted for it: unstyled text is drawn in\n * the client's own default, as Gmail's is. An editor shows it as its\n * surface font, so what is written looks as it will in Gmail; summernote\n * takes its face (`fontName: emailDefaultFont.face`).\n */\nexport const emailDefaultFont: EmailFont = emailFontFamilies[0];\n\n/**\n * The faces of {@link emailFontFamilies}, in order — the set for an editor\n * that offers single families. For summernote, both lists: `fontNamesIgnoreCheck`\n * keeps a face this machine lacks (Garamond, Arial Narrow) in the menu.\n *\n * ```ts\n * $('#editor').summernote({ fontNames: emailFontFaces, fontNamesIgnoreCheck: emailFontFaces });\n * ```\n */\nexport const emailFontFaces: readonly string[] = emailFontFamilies.map((f) => f.face);\n\n/** Every size the editor stores, in px. */\nexport const allowedFontSizes = [10, 12, 14, 16, 18, 24, 32] as const;\nexport type FontSize = (typeof allowedFontSizes)[number];\n\n/**\n * The sizes a picker offers: the phone-safe subset (≥14px). Below ~13px iOS\n * auto-inflates text and reflows the layout, so they are not *offered* —\n * but every {@link allowedFontSizes} size is still read and kept, because a\n * hand-typed size in the HTML source is the author's own responsibility.\n */\nexport const emailFontSizes: readonly FontSize[] = [14, 16, 18, 24, 32];\n\n/** A `font-family` value as a comparison key: lower-cased, quotes dropped,\n whitespace around commas collapsed — so a hand-typed or CSSOM-reserialized\n stack matches a curated one regardless of cosmetic differences. */\nexport function fontFamilyKey(raw: string): string {\n return raw\n .toLowerCase()\n .replace(/[\"']/g, '')\n .replace(/\\s*,\\s*/g, ',')\n .trim();\n}\n\nconst FONT_BY_KEY = new Map(emailFontFamilies.map((f) => [fontFamilyKey(f.stack), f]));\n\n/** Each family a stack names to the first stack naming it. */\nconst FONT_BY_FAMILY = new Map<string, EmailFont>();\nfor (const f of emailFontFamilies) {\n for (const family of fontFamilyKey(f.stack).split(',')) {\n if (!FONT_BY_FAMILY.has(family)) FONT_BY_FAMILY.set(family, f);\n }\n}\n\n/** Whether `value` is exactly one of the stacks, give or take quotes, case\n and spacing. */\nexport function isEmailFontStack(value: unknown): value is string {\n return typeof value === 'string' && FONT_BY_KEY.has(fontFamilyKey(value));\n}\n\n/**\n * The font a `font-family` value is. One of the stacks is itself; a stack of\n * someone else's is the font it falls back to — the first of its families a\n * stack names, its generic last: a builder's web font (`Ubuntu, Helvetica,\n * Arial, sans-serif`) is Sans Serif, the face a client without that font\n * draws anyway, and summernote's `'Comic Sans MS'` is Comic Sans MS.\n * Nothing to fall back to, no font.\n */\nexport function matchEmailFont(raw: string | null | undefined): EmailFont | null {\n if (!raw) return null;\n const key = fontFamilyKey(raw);\n const exact = FONT_BY_KEY.get(key);\n if (exact) return exact;\n for (const family of key.split(',')) {\n const found = FONT_BY_FAMILY.get(family.trim());\n if (found) return found;\n }\n return null;\n}\n","/*\n * Public API Surface of @h-k-dev/angular-email-editor/fonts\n *\n * Its own entry because it is plain data: a host whose other composer is\n * not this editor (summernote) shares the font set without pulling in\n * ProseMirror or Angular.\n */\n\nexport * from './fonts';\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":[],"mappings":"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"}
@@ -240,11 +240,26 @@ class SuggestionMenu {
240
240
  // Placing the box: after its rows are rendered, measured in one phase and
241
241
  // written in the next — the plugin never touches the element, so no read
242
242
  // follows a write inside a keystroke. Shown only once it is in place.
243
+ //
244
+ // A box that fits on neither side of the line is shrunk to the side with
245
+ // more room. The cap goes on the listbox, not the box: the box keeps its
246
+ // own max-height (the --email-suggestion-menu-max-height token) to be
247
+ // read, and its height without the cap is its rendered height plus what
248
+ // the capped list hides — a box measured at its last cap would choose its
249
+ // side by the old room and flip back and forth.
243
250
  afterRenderEffect({
244
251
  earlyRead: () => {
245
252
  const layout = this.#layout();
246
253
  const anchor = layout?.clientRect();
247
- return layout && anchor ? measureMenuPlacement(anchor, this.#host, layout.offset) : null;
254
+ if (!layout || !anchor)
255
+ return null;
256
+ const list = this.listbox().nativeElement;
257
+ const rendered = this.#host.offsetHeight;
258
+ const cap = parseFloat(getComputedStyle(this.#host).maxHeight);
259
+ const full = rendered + list.scrollHeight - list.clientHeight;
260
+ const height = cap >= 0 ? Math.min(full, cap) : full;
261
+ const place = measureMenuPlacement(anchor, this.#host, layout.offset, height);
262
+ return place && { ...place, chrome: rendered - list.offsetHeight };
248
263
  },
249
264
  write: (placement) => {
250
265
  const place = placement();
@@ -253,10 +268,14 @@ class SuggestionMenu {
253
268
  if (place) {
254
269
  const left = `${place.left}px`;
255
270
  const top = `${place.top}px`;
271
+ const listHeight = place.maxHeight === null ? '' : `${Math.max(place.maxHeight - place.chrome, 0)}px`;
272
+ const list = this.listbox().nativeElement.style;
256
273
  if (style.left !== left)
257
274
  style.left = left;
258
275
  if (style.top !== top)
259
276
  style.top = top;
277
+ if (list.maxHeight !== listHeight)
278
+ list.maxHeight = listHeight;
260
279
  }
261
280
  if (style.visibility !== visibility)
262
281
  style.visibility = visibility;
@@ -1 +1 @@
1
- {"version":3,"file":"h-k-dev-angular-email-editor-suggestion-menu.mjs","sources":["../../../projects/angular-email-editor/suggestion-menu/suggestion-menu.slots.ts","../../../projects/angular-email-editor/suggestion-menu/suggestion-menu.ts","../../../projects/angular-email-editor/suggestion-menu/suggestion-menu.html","../../../projects/angular-email-editor/suggestion-menu/suggestion-menu-item.ts","../../../projects/angular-email-editor/suggestion-menu/suggestion-menu-section.ts","../../../projects/angular-email-editor/suggestion-menu/public-api.ts","../../../projects/angular-email-editor/suggestion-menu/h-k-dev-angular-email-editor-suggestion-menu.ts"],"sourcesContent":["import { Directive, TemplateRef, inject } from '@angular/core';\nimport { SuggestionGroup } from '@h-k-dev/angular-email-editor';\n\n/**\n * A suggestion menu's header slot, shown on level 2 above the group's\n * children. The default is a back button and the group's title; a host\n * that wants its own — a Material icon button, the group's icon, a\n * breadcrumb — hands the menu a template:\n *\n * <div email-suggestion-menu [state]=\"state()\">\n * <ng-template emailSuggestionMenuHeader let-group let-back=\"back\" let-label=\"backLabel\">\n * <button mat-icon-button tabindex=\"-1\" [attr.aria-label]=\"label\" (click)=\"back()\">\n * <mat-icon>arrow_back</mat-icon>\n * </button>\n * {{ group.title }}\n * </ng-template>\n * …\n * </div>\n *\n * The template renders inside the menu's own `header` box.\n */\n\n/** What the header template is given. */\nexport interface SuggestionMenuHeaderContext {\n /** The group whose children are listed — its title already translated. */\n readonly $implicit: SuggestionGroup;\n /** Back to level 1 with an empty query. */\n readonly back: () => void;\n /** The back control's accessible name, as the menu was given it. */\n readonly backLabel: string;\n}\n\n/** Marks the `ng-template` the menu renders as its level-2 header. */\n@Directive({ selector: 'ng-template[emailSuggestionMenuHeader]' })\nexport class SuggestionMenuHeader {\n readonly template = inject<TemplateRef<SuggestionMenuHeaderContext>>(TemplateRef);\n\n static ngTemplateContextGuard(\n _directive: SuggestionMenuHeader,\n context: unknown,\n ): context is SuggestionMenuHeaderContext {\n return true;\n }\n}\n","import {\n Component,\n DestroyRef,\n ElementRef,\n inject,\n\n // Signals\n afterRenderEffect,\n computed,\n contentChild,\n debounced,\n input,\n linkedSignal,\n viewChild,\n} from '@angular/core';\nimport { NgTemplateOutlet } from '@angular/common';\nimport { SuggestionItem, SuggestionMenuState, measureMenuPlacement } from '@h-k-dev/angular-email-editor';\nimport { SuggestionMenuHeader, SuggestionMenuHeaderContext } from './suggestion-menu.slots';\n\n/**\n * A suggestion menu's floating box — the element `createSuggestionMenu` is\n * given, rendered from the state it reports. The extension does the work\n * (finding the trigger, filtering, paging, the keys, placing the box,\n * applying); this gives the state its roles and ids, so the editor's\n * `aria-controls` and `aria-activedescendant` land on real elements, keeps\n * the next page coming as the list scrolls, and says what is going on.\n *\n * **The pair.** The menu is the container; the host renders one\n * `[email-suggestion-menu-item]` per item into it and decides what a row\n * shows (an icon, the title, a detail). Rows are options of the menu's\n * listbox and never take focus: the caret stays in the editor, which is the\n * combobox. One pair serves every trigger — `/`, `{{`, `||` — at once: they\n * never open together, so a menu of several triggers is still one element\n * and one state, which says whose list it is (`trigger`, `label`).\n *\n * <div email-suggestion-menu #menu [state]=\"state()\">\n * @for (item of state()?.items; track item.id) {\n * <div email-suggestion-menu-item [item]=\"item\">{{ item.title }}</div>\n * }\n * </div>\n *\n * createSuggestionMenu({ element: menu, onChange: (s) => state.set(s), triggers: [\n * { trigger: '/', label: 'Insert block', items: extensionSuggestions },\n * { trigger: '{{', label: 'Tokens', source: fetchMergeTags },\n * ] })\n *\n * **Two levels.** Inside a group the menu shows a header above the list — a\n * back button and the group's title by default, or the host's\n * `ng-template[emailSuggestionMenuHeader]` — names the listbox after the\n * group, and says \"No results\" rather than closing when nothing matches.\n *\n * **Said twice, differently.** What is going on shows as a line under the\n * list, at once. A screen reader hears it from a separate live region that\n * waits for the typing to settle (`announceDelay`) and also counts the\n * results — the highlighted option's id does not change when the list is\n * filtered under it, so nothing else would say the list is a new one.\n *\n * Every word it says is an input, English by default.\n */\n@Component({\n selector: '[email-suggestion-menu]',\n imports: [NgTemplateOutlet],\n templateUrl: './suggestion-menu.html',\n styleUrl: './suggestion-menu.scss',\n host: {\n '[attr.data-level]': 'state()?.level ?? 1',\n },\n})\nexport class SuggestionMenu {\n /** The extension's latest state — undefined before the editor mounts. */\n readonly state = input<SuggestionMenuState>();\n\n /** Names the listbox on level 1 when the open trigger has no `label` of\n its own — a menu of several triggers names each list there (\"Insert\n block\", \"Personalization tokens\"); level 2 is named by its group. */\n readonly label = input('Suggestions');\n /** The level-2 back button's accessible name. */\n readonly backLabel = input('Back');\n /** Said while a source is still searching. */\n readonly loadingLabel = input('Searching…');\n /** Said on level 2 when nothing matches. */\n readonly emptyLabel = input('No results');\n /** Said while a further page is on its way. */\n readonly loadingMoreLabel = input('Loading more…');\n /** Said when the source's answer failed. */\n readonly errorLabel = input('Couldn’t load suggestions');\n /** Announced once a list is in: how many rows it has. */\n readonly resultsLabel = input<(count: number) => string>((count) =>\n count === 1 ? '1 result' : `${count} results`,\n );\n /** Milliseconds the announcement waits for the typing to settle, so a\n screen reader hears the list that stayed, not one per letter. Going\n quiet (the menu closes) never waits. 0 announces at once. */\n readonly announceDelay = input(400);\n /** Gap between the trigger's line and the menu, in px. */\n readonly offset = input(4);\n\n readonly #host = inject<ElementRef<HTMLElement>>(ElementRef).nativeElement;\n\n /** The state's list. The extension reports a new state for every arrow\n press but the same list until it changes — so what hangs off the list\n (the index below, every row's position) sits an arrow press out. */\n readonly #items = computed(() => this.state()?.items);\n\n /** Each item's position in the list, looked up once per list — rows ask\n by identity, so a list of n rows costs n lookups, not n². */\n readonly #indices = computed(\n () => new Map(this.#items()?.map((item, index) => [item, index]) ?? []),\n );\n\n /** The keyboard-highlighted row's index; -1 before there is a state. */\n readonly activeIndex = computed(() => this.state()?.activeIndex ?? -1);\n\n /** The extension's id for the option at an index — the same function for\n the menu's whole life. */\n readonly optionId = computed(() => this.state()?.optionId);\n\n /** Where `item` sits in the current list; -1 when it is not in it. */\n indexOf(item: SuggestionItem): number {\n return this.#indices().get(item) ?? -1;\n }\n\n protected readonly header = contentChild(SuggestionMenuHeader);\n protected readonly listbox = viewChild.required<ElementRef<HTMLElement>>('listbox');\n protected readonly end = viewChild.required<ElementRef<HTMLElement>>('end');\n\n /** The group being searched, on level 2. */\n protected readonly parent = computed(() => this.state()?.parent ?? null);\n\n protected readonly listLabel = computed(\n () => this.parent()?.title ?? this.state()?.label ?? this.label(),\n );\n\n /** The header template's context, while there is a group to head. */\n protected readonly headerContext = computed((): SuggestionMenuHeaderContext | null => {\n const group = this.parent();\n return group && { $implicit: group, back: this.back, backLabel: this.backLabel() };\n });\n\n /** The line under the list: failed, searching, loading more, nothing\n found, or nothing. Not \"searching\" over stale rows — dimmed, they say\n it themselves, and a line that came and went with every keystroke\n would resize the box (and have it placed again) twice per letter. */\n protected readonly status = computed(() => {\n const state = this.state();\n if (!state?.open) return '';\n if (state.error !== null) return this.errorLabel();\n if (state.loading) return state.stale ? '' : this.loadingLabel();\n if (state.loadingMore) return this.loadingMoreLabel();\n if (state.level === 2 && !state.items.length) return this.emptyLabel();\n return '';\n });\n\n /** What a screen reader should hear next: the status line, or how many\n rows the list has now. While the rows on screen answer the previous\n query there is no news yet — what was said last stands. */\n readonly #announcement = linkedSignal<\n { state: SuggestionMenuState | undefined; status: string; results: (count: number) => string },\n string\n >({\n source: () => ({ state: this.state(), status: this.status(), results: this.resultsLabel() }),\n computation: ({ state, status, results }, previous) => {\n if (!state?.open) return '';\n if (status) return status;\n if (state.loading) return previous?.value ?? '';\n return results(state.items.length);\n },\n });\n\n /** The announcement, once the typing has settled. */\n protected readonly announced = debounced(this.#announcement, (text) => {\n const delay = this.announceDelay();\n return text && delay > 0\n ? new Promise<void>((resolve) => setTimeout(resolve, delay))\n : undefined;\n });\n\n /** A page can be asked for right now: more exist and nothing is out. */\n readonly #canLoadMore = computed(() => {\n const state = this.state();\n return !!state?.open && state.hasMore && !state.loading && !state.loadingMore;\n });\n\n /** What the box's place depends on: where the session starts, and what\n decides its size (rows, header, status line, query). An arrow press\n changes none of it, so it measures nothing. */\n readonly #layout = computed(\n () => {\n const state = this.state();\n if (!state?.open || !state.range) return null;\n return {\n from: state.range.from,\n rows: state.items.length,\n level: state.level,\n query: state.query,\n status: this.status(),\n clientRect: state.clientRect,\n offset: this.offset(),\n };\n },\n {\n equal: (a, b) =>\n a === b ||\n (!!a &&\n !!b &&\n a.from === b.from &&\n a.rows === b.rows &&\n a.level === b.level &&\n a.query === b.query &&\n a.status === b.status &&\n a.offset === b.offset),\n },\n );\n\n /** What moves the highlight: the row it is on, and the list it is in — a\n level, a query. Null while closed. */\n readonly #highlight = computed(() => {\n const state = this.state();\n return state?.open ? `${state.level}:${state.query}:${state.activeIndex}` : null;\n });\n\n /** Watches the list's end marker: its coming within two rows of the\n listbox's bottom edge asks for the next page. An observer, not a scroll\n listener — it speaks only when the marker crosses in, so scrolling\n costs nothing. Made on first use: its root is the rendered listbox. */\n #endObserver: IntersectionObserver | undefined;\n\n constructor() {\n inject(DestroyRef).onDestroy(() => this.#endObserver?.disconnect());\n\n // Watched only while a page can be loaded. Observing afresh reports the\n // marker's current state, so a page too short to fill the box (the\n // marker still in view once it lands) asks for the next one straight\n // away, and a list that scrolls waits for the scroll.\n afterRenderEffect({\n read: () => {\n if (typeof IntersectionObserver === 'undefined') return;\n const canLoad = this.#canLoadMore();\n // Any new list re-arms the watch, not only a longer one: a source\n // that answers at once never passes through `loading`, and its next\n // list can have as many rows as the last.\n this.#items();\n const end = this.end().nativeElement;\n this.#endObserver ??= new IntersectionObserver(\n (entries) => {\n if (entries.some((entry) => entry.isIntersecting)) this.state()?.loadMore();\n },\n { root: this.listbox().nativeElement, rootMargin: '0px 0px 64px 0px' },\n );\n this.#endObserver.unobserve(end);\n if (canLoad) this.#endObserver.observe(end);\n },\n });\n\n // Placing the box: after its rows are rendered, measured in one phase and\n // written in the next — the plugin never touches the element, so no read\n // follows a write inside a keystroke. Shown only once it is in place.\n afterRenderEffect({\n earlyRead: () => {\n const layout = this.#layout();\n const anchor = layout?.clientRect();\n return layout && anchor ? measureMenuPlacement(anchor, this.#host, layout.offset) : null;\n },\n write: (placement) => {\n const place = placement();\n const style = this.#host.style;\n const visibility = place ? 'visible' : 'hidden';\n if (place) {\n const left = `${place.left}px`;\n const top = `${place.top}px`;\n if (style.left !== left) style.left = left;\n if (style.top !== top) style.top = top;\n }\n if (style.visibility !== visibility) style.visibility = visibility;\n },\n });\n\n // The highlighted row stays visible as the arrows walk past the fold —\n // one watch for the whole list, not one per row: measured in one phase,\n // scrolled in the next, the listbox only. A new query is a new list\n // (back at its top row); a page landing under a list scrolled by hand is\n // not, and must not pull it back.\n afterRenderEffect({\n earlyRead: () => {\n if (this.#highlight() === null) return null;\n const list = this.listbox().nativeElement;\n const row = list.querySelector<HTMLElement>('[data-active]');\n if (!row) return null;\n const { offsetTop: top, offsetHeight: height } = row;\n const { scrollTop, clientHeight } = list;\n if (top < scrollTop) return { list, scrollTop: top };\n if (top + height > scrollTop + clientHeight) {\n return { list, scrollTop: top + height - clientHeight };\n }\n return null;\n },\n write: (scroll) => {\n const target = scroll();\n if (target) target.list.scrollTop = target.scrollTop;\n },\n });\n\n // A level starts at its top: entering a group or leaving one does not\n // keep the other list's scroll position.\n afterRenderEffect({\n write: () => {\n this.parent();\n this.listbox().nativeElement.scrollTop = 0;\n },\n });\n }\n\n protected readonly back = (): void => this.state()?.back();\n}\n","@if (parent(); as group) {\n <div data-slot=\"header\">\n @if (header(); as slot) {\n <ng-container *ngTemplateOutlet=\"slot.template; context: headerContext()\" />\n } @else {\n <!-- The pointer's way back; the keyboard's is Backspace in the editor,\n which keeps the focus — so this never joins the tab order. -->\n <button\n type=\"button\"\n data-slot=\"back\"\n tabindex=\"-1\"\n [attr.aria-label]=\"backLabel()\"\n (click)=\"back()\"\n >\n <svg viewBox=\"0 0 24 24\" aria-hidden=\"true\" focusable=\"false\">\n <path d=\"M15.41 7.41 14 6l-6 6 6 6 1.41-1.41L10.83 12z\" />\n </svg>\n </button>\n <span data-slot=\"title\">{{ group.title }}</span>\n }\n </div>\n}\n\n<div\n #listbox\n role=\"listbox\"\n data-slot=\"listbox\"\n [attr.id]=\"state()?.listboxId\"\n [attr.aria-label]=\"listLabel()\"\n [attr.aria-busy]=\"state()?.loading || state()?.loadingMore || null\"\n [attr.data-stale]=\"state()?.stale || null\"\n>\n <ng-content />\n <!-- The end of the list, watched for the next page (never an option). -->\n <div #end data-slot=\"end\" role=\"none\" aria-hidden=\"true\"></div>\n</div>\n\n<!-- What is going on, for the eye: at once, collapsed while there is nothing\n to say. -->\n<div\n data-slot=\"status\"\n aria-hidden=\"true\"\n [attr.data-empty]=\"!status() || null\"\n [textContent]=\"status()\"\n></div>\n\n<!-- …and for a screen reader: settled, and with the result count. Always in\n the accessibility tree — visible while the box around it is hidden — so\n the first words of an opening menu are heard too. -->\n<div role=\"status\" data-slot=\"announcer\">{{ announced.value() }}</div>\n","import { Component, computed, inject, input } from '@angular/core';\nimport { SuggestionItem } from '@h-k-dev/angular-email-editor';\nimport { SuggestionMenu } from './suggestion-menu';\n\n/**\n * One row of a suggestion menu: an option of its listbox, standing for one of\n * the state's items. The host fills the row; the row takes its id (the one\n * the editor's `aria-activedescendant` names), reflects the highlight as\n * `aria-selected` and `data-active` — which is what the menu keeps scrolled\n * into view — and applies its item on click: a group opens, a command runs.\n *\n * Never focusable, never a button: the caret stays in the editor, and the\n * extension keeps a press on the menu from taking it away.\n */\n@Component({\n selector: '[email-suggestion-menu-item]',\n template: '<ng-content />',\n styleUrl: './suggestion-menu-item.scss',\n host: {\n role: 'option',\n '[attr.id]': 'id()',\n '[attr.aria-selected]': 'active()',\n '[attr.data-active]': 'active() || null',\n '[attr.data-group]': '!!item().children || null',\n '(click)': 'select()',\n },\n})\nexport class SuggestionMenuItem {\n /** The item this row stands for — one of the menu state's `items`. */\n readonly item = input.required<SuggestionItem>();\n\n readonly #menu = inject(SuggestionMenu);\n\n readonly #index = computed(() => this.#menu.indexOf(this.item()));\n\n /** The keyboard highlight is on this row. */\n readonly active = computed(() => {\n const index = this.#index();\n return index >= 0 && index === this.#menu.activeIndex();\n });\n\n protected readonly id = computed(() => {\n const optionId = this.#menu.optionId();\n const index = this.#index();\n return optionId && index >= 0 ? optionId(index) : null;\n });\n\n protected select(): void {\n this.#menu.state()?.select(this.item());\n }\n}\n","import { Component } from '@angular/core';\n\n/**\n * A heading in a suggestion menu's list: the words of a section\n * (`SuggestionItem.section`, worded by the state's `sectionTitle`), shown\n * where the section changes from one row to the next. Never an option: it\n * takes no highlight, no click, and no place in the count — presentation,\n * the way a chat's slash menu heads its groups.\n *\n * @for (item of state.items; track item.id) {\n * @if (item.section && item.section !== state.items[$index - 1]?.section) {\n * <div email-suggestion-menu-section>{{ state.sectionTitle(item.section) }}</div>\n * }\n * <div email-suggestion-menu-item [item]=\"item\">…</div>\n * }\n */\n@Component({\n selector: '[email-suggestion-menu-section]',\n template: '<ng-content />',\n styleUrl: './suggestion-menu-section.scss',\n host: { role: 'presentation' },\n})\nexport class SuggestionMenuSection {}\n","/*\n * Public API Surface of @h-k-dev/angular-email-editor/suggestion-menu\n */\n\nexport * from './suggestion-menu';\nexport * from './suggestion-menu-item';\nexport * from './suggestion-menu.slots';\nexport * from './suggestion-menu-section';\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"h-k-dev-angular-email-editor-suggestion-menu.mjs","sources":["../../../projects/angular-email-editor/suggestion-menu/suggestion-menu.slots.ts","../../../projects/angular-email-editor/suggestion-menu/suggestion-menu.ts","../../../projects/angular-email-editor/suggestion-menu/suggestion-menu.html","../../../projects/angular-email-editor/suggestion-menu/suggestion-menu-item.ts","../../../projects/angular-email-editor/suggestion-menu/suggestion-menu-section.ts","../../../projects/angular-email-editor/suggestion-menu/public-api.ts","../../../projects/angular-email-editor/suggestion-menu/h-k-dev-angular-email-editor-suggestion-menu.ts"],"sourcesContent":["import { Directive, TemplateRef, inject } from '@angular/core';\nimport { SuggestionGroup } from '@h-k-dev/angular-email-editor';\n\n/**\n * A suggestion menu's header slot, shown on level 2 above the group's\n * children. The default is a back button and the group's title; a host\n * that wants its own — a Material icon button, the group's icon, a\n * breadcrumb — hands the menu a template:\n *\n * <div email-suggestion-menu [state]=\"state()\">\n * <ng-template emailSuggestionMenuHeader let-group let-back=\"back\" let-label=\"backLabel\">\n * <button mat-icon-button tabindex=\"-1\" [attr.aria-label]=\"label\" (click)=\"back()\">\n * <mat-icon>arrow_back</mat-icon>\n * </button>\n * {{ group.title }}\n * </ng-template>\n * …\n * </div>\n *\n * The template renders inside the menu's own `header` box.\n */\n\n/** What the header template is given. */\nexport interface SuggestionMenuHeaderContext {\n /** The group whose children are listed — its title already translated. */\n readonly $implicit: SuggestionGroup;\n /** Back to level 1 with an empty query. */\n readonly back: () => void;\n /** The back control's accessible name, as the menu was given it. */\n readonly backLabel: string;\n}\n\n/** Marks the `ng-template` the menu renders as its level-2 header. */\n@Directive({ selector: 'ng-template[emailSuggestionMenuHeader]' })\nexport class SuggestionMenuHeader {\n readonly template = inject<TemplateRef<SuggestionMenuHeaderContext>>(TemplateRef);\n\n static ngTemplateContextGuard(\n _directive: SuggestionMenuHeader,\n context: unknown,\n ): context is SuggestionMenuHeaderContext {\n return true;\n }\n}\n","import {\n Component,\n DestroyRef,\n ElementRef,\n inject,\n\n // Signals\n afterRenderEffect,\n computed,\n contentChild,\n debounced,\n input,\n linkedSignal,\n viewChild,\n} from '@angular/core';\nimport { NgTemplateOutlet } from '@angular/common';\nimport { SuggestionItem, SuggestionMenuState, measureMenuPlacement } from '@h-k-dev/angular-email-editor';\nimport { SuggestionMenuHeader, SuggestionMenuHeaderContext } from './suggestion-menu.slots';\n\n/**\n * A suggestion menu's floating box — the element `createSuggestionMenu` is\n * given, rendered from the state it reports. The extension does the work\n * (finding the trigger, filtering, paging, the keys, placing the box,\n * applying); this gives the state its roles and ids, so the editor's\n * `aria-controls` and `aria-activedescendant` land on real elements, keeps\n * the next page coming as the list scrolls, and says what is going on.\n *\n * **The pair.** The menu is the container; the host renders one\n * `[email-suggestion-menu-item]` per item into it and decides what a row\n * shows (an icon, the title, a detail). Rows are options of the menu's\n * listbox and never take focus: the caret stays in the editor, which is the\n * combobox. One pair serves every trigger — `/`, `{{`, `||` — at once: they\n * never open together, so a menu of several triggers is still one element\n * and one state, which says whose list it is (`trigger`, `label`).\n *\n * <div email-suggestion-menu #menu [state]=\"state()\">\n * @for (item of state()?.items; track item.id) {\n * <div email-suggestion-menu-item [item]=\"item\">{{ item.title }}</div>\n * }\n * </div>\n *\n * createSuggestionMenu({ element: menu, onChange: (s) => state.set(s), triggers: [\n * { trigger: '/', label: 'Insert block', items: extensionSuggestions },\n * { trigger: '{{', label: 'Tokens', source: fetchMergeTags },\n * ] })\n *\n * **Two levels.** Inside a group the menu shows a header above the list — a\n * back button and the group's title by default, or the host's\n * `ng-template[emailSuggestionMenuHeader]` — names the listbox after the\n * group, and says \"No results\" rather than closing when nothing matches.\n *\n * **Said twice, differently.** What is going on shows as a line under the\n * list, at once. A screen reader hears it from a separate live region that\n * waits for the typing to settle (`announceDelay`) and also counts the\n * results — the highlighted option's id does not change when the list is\n * filtered under it, so nothing else would say the list is a new one.\n *\n * Every word it says is an input, English by default.\n */\n@Component({\n selector: '[email-suggestion-menu]',\n imports: [NgTemplateOutlet],\n templateUrl: './suggestion-menu.html',\n styleUrl: './suggestion-menu.scss',\n host: {\n '[attr.data-level]': 'state()?.level ?? 1',\n },\n})\nexport class SuggestionMenu {\n /** The extension's latest state — undefined before the editor mounts. */\n readonly state = input<SuggestionMenuState>();\n\n /** Names the listbox on level 1 when the open trigger has no `label` of\n its own — a menu of several triggers names each list there (\"Insert\n block\", \"Personalization tokens\"); level 2 is named by its group. */\n readonly label = input('Suggestions');\n /** The level-2 back button's accessible name. */\n readonly backLabel = input('Back');\n /** Said while a source is still searching. */\n readonly loadingLabel = input('Searching…');\n /** Said on level 2 when nothing matches. */\n readonly emptyLabel = input('No results');\n /** Said while a further page is on its way. */\n readonly loadingMoreLabel = input('Loading more…');\n /** Said when the source's answer failed. */\n readonly errorLabel = input('Couldn’t load suggestions');\n /** Announced once a list is in: how many rows it has. */\n readonly resultsLabel = input<(count: number) => string>((count) =>\n count === 1 ? '1 result' : `${count} results`,\n );\n /** Milliseconds the announcement waits for the typing to settle, so a\n screen reader hears the list that stayed, not one per letter. Going\n quiet (the menu closes) never waits. 0 announces at once. */\n readonly announceDelay = input(400);\n /** Gap between the trigger's line and the menu, in px. */\n readonly offset = input(4);\n\n readonly #host = inject<ElementRef<HTMLElement>>(ElementRef).nativeElement;\n\n /** The state's list. The extension reports a new state for every arrow\n press but the same list until it changes — so what hangs off the list\n (the index below, every row's position) sits an arrow press out. */\n readonly #items = computed(() => this.state()?.items);\n\n /** Each item's position in the list, looked up once per list — rows ask\n by identity, so a list of n rows costs n lookups, not n². */\n readonly #indices = computed(\n () => new Map(this.#items()?.map((item, index) => [item, index]) ?? []),\n );\n\n /** The keyboard-highlighted row's index; -1 before there is a state. */\n readonly activeIndex = computed(() => this.state()?.activeIndex ?? -1);\n\n /** The extension's id for the option at an index — the same function for\n the menu's whole life. */\n readonly optionId = computed(() => this.state()?.optionId);\n\n /** Where `item` sits in the current list; -1 when it is not in it. */\n indexOf(item: SuggestionItem): number {\n return this.#indices().get(item) ?? -1;\n }\n\n protected readonly header = contentChild(SuggestionMenuHeader);\n protected readonly listbox = viewChild.required<ElementRef<HTMLElement>>('listbox');\n protected readonly end = viewChild.required<ElementRef<HTMLElement>>('end');\n\n /** The group being searched, on level 2. */\n protected readonly parent = computed(() => this.state()?.parent ?? null);\n\n protected readonly listLabel = computed(\n () => this.parent()?.title ?? this.state()?.label ?? this.label(),\n );\n\n /** The header template's context, while there is a group to head. */\n protected readonly headerContext = computed((): SuggestionMenuHeaderContext | null => {\n const group = this.parent();\n return group && { $implicit: group, back: this.back, backLabel: this.backLabel() };\n });\n\n /** The line under the list: failed, searching, loading more, nothing\n found, or nothing. Not \"searching\" over stale rows — dimmed, they say\n it themselves, and a line that came and went with every keystroke\n would resize the box (and have it placed again) twice per letter. */\n protected readonly status = computed(() => {\n const state = this.state();\n if (!state?.open) return '';\n if (state.error !== null) return this.errorLabel();\n if (state.loading) return state.stale ? '' : this.loadingLabel();\n if (state.loadingMore) return this.loadingMoreLabel();\n if (state.level === 2 && !state.items.length) return this.emptyLabel();\n return '';\n });\n\n /** What a screen reader should hear next: the status line, or how many\n rows the list has now. While the rows on screen answer the previous\n query there is no news yet — what was said last stands. */\n readonly #announcement = linkedSignal<\n { state: SuggestionMenuState | undefined; status: string; results: (count: number) => string },\n string\n >({\n source: () => ({ state: this.state(), status: this.status(), results: this.resultsLabel() }),\n computation: ({ state, status, results }, previous) => {\n if (!state?.open) return '';\n if (status) return status;\n if (state.loading) return previous?.value ?? '';\n return results(state.items.length);\n },\n });\n\n /** The announcement, once the typing has settled. */\n protected readonly announced = debounced(this.#announcement, (text) => {\n const delay = this.announceDelay();\n return text && delay > 0\n ? new Promise<void>((resolve) => setTimeout(resolve, delay))\n : undefined;\n });\n\n /** A page can be asked for right now: more exist and nothing is out. */\n readonly #canLoadMore = computed(() => {\n const state = this.state();\n return !!state?.open && state.hasMore && !state.loading && !state.loadingMore;\n });\n\n /** What the box's place depends on: where the session starts, and what\n decides its size (rows, header, status line, query). An arrow press\n changes none of it, so it measures nothing. */\n readonly #layout = computed(\n () => {\n const state = this.state();\n if (!state?.open || !state.range) return null;\n return {\n from: state.range.from,\n rows: state.items.length,\n level: state.level,\n query: state.query,\n status: this.status(),\n clientRect: state.clientRect,\n offset: this.offset(),\n };\n },\n {\n equal: (a, b) =>\n a === b ||\n (!!a &&\n !!b &&\n a.from === b.from &&\n a.rows === b.rows &&\n a.level === b.level &&\n a.query === b.query &&\n a.status === b.status &&\n a.offset === b.offset),\n },\n );\n\n /** What moves the highlight: the row it is on, and the list it is in — a\n level, a query. Null while closed. */\n readonly #highlight = computed(() => {\n const state = this.state();\n return state?.open ? `${state.level}:${state.query}:${state.activeIndex}` : null;\n });\n\n /** Watches the list's end marker: its coming within two rows of the\n listbox's bottom edge asks for the next page. An observer, not a scroll\n listener — it speaks only when the marker crosses in, so scrolling\n costs nothing. Made on first use: its root is the rendered listbox. */\n #endObserver: IntersectionObserver | undefined;\n\n constructor() {\n inject(DestroyRef).onDestroy(() => this.#endObserver?.disconnect());\n\n // Watched only while a page can be loaded. Observing afresh reports the\n // marker's current state, so a page too short to fill the box (the\n // marker still in view once it lands) asks for the next one straight\n // away, and a list that scrolls waits for the scroll.\n afterRenderEffect({\n read: () => {\n if (typeof IntersectionObserver === 'undefined') return;\n const canLoad = this.#canLoadMore();\n // Any new list re-arms the watch, not only a longer one: a source\n // that answers at once never passes through `loading`, and its next\n // list can have as many rows as the last.\n this.#items();\n const end = this.end().nativeElement;\n this.#endObserver ??= new IntersectionObserver(\n (entries) => {\n if (entries.some((entry) => entry.isIntersecting)) this.state()?.loadMore();\n },\n { root: this.listbox().nativeElement, rootMargin: '0px 0px 64px 0px' },\n );\n this.#endObserver.unobserve(end);\n if (canLoad) this.#endObserver.observe(end);\n },\n });\n\n // Placing the box: after its rows are rendered, measured in one phase and\n // written in the next — the plugin never touches the element, so no read\n // follows a write inside a keystroke. Shown only once it is in place.\n //\n // A box that fits on neither side of the line is shrunk to the side with\n // more room. The cap goes on the listbox, not the box: the box keeps its\n // own max-height (the --email-suggestion-menu-max-height token) to be\n // read, and its height without the cap is its rendered height plus what\n // the capped list hides — a box measured at its last cap would choose its\n // side by the old room and flip back and forth.\n afterRenderEffect({\n earlyRead: () => {\n const layout = this.#layout();\n const anchor = layout?.clientRect();\n if (!layout || !anchor) return null;\n const list = this.listbox().nativeElement;\n const rendered = this.#host.offsetHeight;\n const cap = parseFloat(getComputedStyle(this.#host).maxHeight);\n const full = rendered + list.scrollHeight - list.clientHeight;\n const height = cap >= 0 ? Math.min(full, cap) : full;\n const place = measureMenuPlacement(anchor, this.#host, layout.offset, height);\n return place && { ...place, chrome: rendered - list.offsetHeight };\n },\n write: (placement) => {\n const place = placement();\n const style = this.#host.style;\n const visibility = place ? 'visible' : 'hidden';\n if (place) {\n const left = `${place.left}px`;\n const top = `${place.top}px`;\n const listHeight =\n place.maxHeight === null ? '' : `${Math.max(place.maxHeight - place.chrome, 0)}px`;\n const list = this.listbox().nativeElement.style;\n if (style.left !== left) style.left = left;\n if (style.top !== top) style.top = top;\n if (list.maxHeight !== listHeight) list.maxHeight = listHeight;\n }\n if (style.visibility !== visibility) style.visibility = visibility;\n },\n });\n\n // The highlighted row stays visible as the arrows walk past the fold —\n // one watch for the whole list, not one per row: measured in one phase,\n // scrolled in the next, the listbox only. A new query is a new list\n // (back at its top row); a page landing under a list scrolled by hand is\n // not, and must not pull it back.\n afterRenderEffect({\n earlyRead: () => {\n if (this.#highlight() === null) return null;\n const list = this.listbox().nativeElement;\n const row = list.querySelector<HTMLElement>('[data-active]');\n if (!row) return null;\n const { offsetTop: top, offsetHeight: height } = row;\n const { scrollTop, clientHeight } = list;\n if (top < scrollTop) return { list, scrollTop: top };\n if (top + height > scrollTop + clientHeight) {\n return { list, scrollTop: top + height - clientHeight };\n }\n return null;\n },\n write: (scroll) => {\n const target = scroll();\n if (target) target.list.scrollTop = target.scrollTop;\n },\n });\n\n // A level starts at its top: entering a group or leaving one does not\n // keep the other list's scroll position.\n afterRenderEffect({\n write: () => {\n this.parent();\n this.listbox().nativeElement.scrollTop = 0;\n },\n });\n }\n\n protected readonly back = (): void => this.state()?.back();\n}\n","@if (parent(); as group) {\n <div data-slot=\"header\">\n @if (header(); as slot) {\n <ng-container *ngTemplateOutlet=\"slot.template; context: headerContext()\" />\n } @else {\n <!-- The pointer's way back; the keyboard's is Backspace in the editor,\n which keeps the focus — so this never joins the tab order. -->\n <button\n type=\"button\"\n data-slot=\"back\"\n tabindex=\"-1\"\n [attr.aria-label]=\"backLabel()\"\n (click)=\"back()\"\n >\n <svg viewBox=\"0 0 24 24\" aria-hidden=\"true\" focusable=\"false\">\n <path d=\"M15.41 7.41 14 6l-6 6 6 6 1.41-1.41L10.83 12z\" />\n </svg>\n </button>\n <span data-slot=\"title\">{{ group.title }}</span>\n }\n </div>\n}\n\n<div\n #listbox\n role=\"listbox\"\n data-slot=\"listbox\"\n [attr.id]=\"state()?.listboxId\"\n [attr.aria-label]=\"listLabel()\"\n [attr.aria-busy]=\"state()?.loading || state()?.loadingMore || null\"\n [attr.data-stale]=\"state()?.stale || null\"\n>\n <ng-content />\n <!-- The end of the list, watched for the next page (never an option). -->\n <div #end data-slot=\"end\" role=\"none\" aria-hidden=\"true\"></div>\n</div>\n\n<!-- What is going on, for the eye: at once, collapsed while there is nothing\n to say. -->\n<div\n data-slot=\"status\"\n aria-hidden=\"true\"\n [attr.data-empty]=\"!status() || null\"\n [textContent]=\"status()\"\n></div>\n\n<!-- …and for a screen reader: settled, and with the result count. Always in\n the accessibility tree — visible while the box around it is hidden — so\n the first words of an opening menu are heard too. -->\n<div role=\"status\" data-slot=\"announcer\">{{ announced.value() }}</div>\n","import { Component, computed, inject, input } from '@angular/core';\nimport { SuggestionItem } from '@h-k-dev/angular-email-editor';\nimport { SuggestionMenu } from './suggestion-menu';\n\n/**\n * One row of a suggestion menu: an option of its listbox, standing for one of\n * the state's items. The host fills the row; the row takes its id (the one\n * the editor's `aria-activedescendant` names), reflects the highlight as\n * `aria-selected` and `data-active` — which is what the menu keeps scrolled\n * into view — and applies its item on click: a group opens, a command runs.\n *\n * Never focusable, never a button: the caret stays in the editor, and the\n * extension keeps a press on the menu from taking it away.\n */\n@Component({\n selector: '[email-suggestion-menu-item]',\n template: '<ng-content />',\n styleUrl: './suggestion-menu-item.scss',\n host: {\n role: 'option',\n '[attr.id]': 'id()',\n '[attr.aria-selected]': 'active()',\n '[attr.data-active]': 'active() || null',\n '[attr.data-group]': '!!item().children || null',\n '(click)': 'select()',\n },\n})\nexport class SuggestionMenuItem {\n /** The item this row stands for — one of the menu state's `items`. */\n readonly item = input.required<SuggestionItem>();\n\n readonly #menu = inject(SuggestionMenu);\n\n readonly #index = computed(() => this.#menu.indexOf(this.item()));\n\n /** The keyboard highlight is on this row. */\n readonly active = computed(() => {\n const index = this.#index();\n return index >= 0 && index === this.#menu.activeIndex();\n });\n\n protected readonly id = computed(() => {\n const optionId = this.#menu.optionId();\n const index = this.#index();\n return optionId && index >= 0 ? optionId(index) : null;\n });\n\n protected select(): void {\n this.#menu.state()?.select(this.item());\n }\n}\n","import { Component } from '@angular/core';\n\n/**\n * A heading in a suggestion menu's list: the words of a section\n * (`SuggestionItem.section`, worded by the state's `sectionTitle`), shown\n * where the section changes from one row to the next. Never an option: it\n * takes no highlight, no click, and no place in the count — presentation,\n * the way a chat's slash menu heads its groups.\n *\n * @for (item of state.items; track item.id) {\n * @if (item.section && item.section !== state.items[$index - 1]?.section) {\n * <div email-suggestion-menu-section>{{ state.sectionTitle(item.section) }}</div>\n * }\n * <div email-suggestion-menu-item [item]=\"item\">…</div>\n * }\n */\n@Component({\n selector: '[email-suggestion-menu-section]',\n template: '<ng-content />',\n styleUrl: './suggestion-menu-section.scss',\n host: { role: 'presentation' },\n})\nexport class SuggestionMenuSection {}\n","/*\n * Public API Surface of @h-k-dev/angular-email-editor/suggestion-menu\n */\n\nexport * from './suggestion-menu';\nexport * from './suggestion-menu-item';\nexport * from './suggestion-menu.slots';\nexport * from './suggestion-menu-section';\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":[],"mappings":"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"}