ngwr 13.0.0 → 14.0.0
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 +13 -6
- package/color-picker/styles/_index.scss +43 -5
- package/fesm2022/ngwr-alert.mjs +5 -5
- package/fesm2022/ngwr-alert.mjs.map +1 -1
- package/fesm2022/ngwr-back-top.mjs +11 -1
- package/fesm2022/ngwr-back-top.mjs.map +1 -1
- package/fesm2022/ngwr-blur-text.mjs +13 -2
- package/fesm2022/ngwr-blur-text.mjs.map +1 -1
- package/fesm2022/ngwr-border-glow.mjs +12 -3
- package/fesm2022/ngwr-border-glow.mjs.map +1 -1
- package/fesm2022/ngwr-breadcrumbs.mjs +14 -3
- package/fesm2022/ngwr-breadcrumbs.mjs.map +1 -1
- package/fesm2022/ngwr-button.mjs +4 -4
- package/fesm2022/ngwr-button.mjs.map +1 -1
- package/fesm2022/ngwr-calendar-testing.mjs +15 -0
- package/fesm2022/ngwr-calendar-testing.mjs.map +1 -1
- package/fesm2022/ngwr-calendar.mjs +51 -5
- package/fesm2022/ngwr-calendar.mjs.map +1 -1
- package/fesm2022/ngwr-circular-text.mjs +20 -1
- package/fesm2022/ngwr-circular-text.mjs.map +1 -1
- package/fesm2022/ngwr-collapse.mjs.map +1 -1
- package/fesm2022/ngwr-color-picker-testing.mjs +23 -7
- package/fesm2022/ngwr-color-picker-testing.mjs.map +1 -1
- package/fesm2022/ngwr-color-picker.mjs +89 -10
- package/fesm2022/ngwr-color-picker.mjs.map +1 -1
- package/fesm2022/ngwr-command-palette.mjs +27 -3
- package/fesm2022/ngwr-command-palette.mjs.map +1 -1
- package/fesm2022/ngwr-context-menu.mjs +13 -3
- package/fesm2022/ngwr-context-menu.mjs.map +1 -1
- package/fesm2022/ngwr-date-adapters-fns.mjs +101 -33
- package/fesm2022/ngwr-date-adapters-fns.mjs.map +1 -1
- package/fesm2022/ngwr-date-picker-testing.mjs +10 -2
- package/fesm2022/ngwr-date-picker-testing.mjs.map +1 -1
- package/fesm2022/ngwr-date-picker.mjs +103 -13
- package/fesm2022/ngwr-date-picker.mjs.map +1 -1
- package/fesm2022/ngwr-date.mjs +480 -79
- package/fesm2022/ngwr-date.mjs.map +1 -1
- package/fesm2022/ngwr-dialog.mjs +22 -16
- package/fesm2022/ngwr-dialog.mjs.map +1 -1
- package/fesm2022/ngwr-directives.mjs +5 -2
- package/fesm2022/ngwr-directives.mjs.map +1 -1
- package/fesm2022/ngwr-drag-drop.mjs +30 -2
- package/fesm2022/ngwr-drag-drop.mjs.map +1 -1
- package/fesm2022/ngwr-drawer.mjs +24 -18
- package/fesm2022/ngwr-drawer.mjs.map +1 -1
- package/fesm2022/ngwr-dropdown.mjs +8 -3
- package/fesm2022/ngwr-dropdown.mjs.map +1 -1
- package/fesm2022/ngwr-event-calendar-testing.mjs.map +1 -1
- package/fesm2022/ngwr-event-calendar.mjs +1 -1
- package/fesm2022/ngwr-event-calendar.mjs.map +1 -1
- package/fesm2022/ngwr-file-upload.mjs +52 -12
- package/fesm2022/ngwr-file-upload.mjs.map +1 -1
- package/fesm2022/ngwr-fuzzy-text.mjs +21 -4
- package/fesm2022/ngwr-fuzzy-text.mjs.map +1 -1
- package/fesm2022/ngwr-gradient-text.mjs +9 -2
- package/fesm2022/ngwr-gradient-text.mjs.map +1 -1
- package/fesm2022/ngwr-i18n-en.mjs +63 -1
- package/fesm2022/ngwr-i18n-en.mjs.map +1 -1
- package/fesm2022/ngwr-i18n-ru.mjs +28 -1
- package/fesm2022/ngwr-i18n-ru.mjs.map +1 -1
- package/fesm2022/ngwr-i18n.mjs +150 -22
- package/fesm2022/ngwr-i18n.mjs.map +1 -1
- package/fesm2022/ngwr-icon-adapters-feather.mjs +11 -0
- package/fesm2022/ngwr-icon-adapters-feather.mjs.map +1 -1
- package/fesm2022/ngwr-icon-adapters-lucide.mjs +22 -2
- package/fesm2022/ngwr-icon-adapters-lucide.mjs.map +1 -1
- package/fesm2022/ngwr-icon.mjs +474 -37
- package/fesm2022/ngwr-icon.mjs.map +1 -1
- package/fesm2022/ngwr-input-number.mjs +3 -3
- package/fesm2022/ngwr-input-number.mjs.map +1 -1
- package/fesm2022/ngwr-input-otp.mjs +21 -3
- package/fesm2022/ngwr-input-otp.mjs.map +1 -1
- package/fesm2022/ngwr-input.mjs +14 -7
- package/fesm2022/ngwr-input.mjs.map +1 -1
- package/fesm2022/ngwr-knob-testing.mjs.map +1 -1
- package/fesm2022/ngwr-knob.mjs +20 -2
- package/fesm2022/ngwr-knob.mjs.map +1 -1
- package/fesm2022/ngwr-loading-bar-router.mjs +73 -0
- package/fesm2022/ngwr-loading-bar-router.mjs.map +1 -0
- package/fesm2022/ngwr-loading-bar.mjs +21 -32
- package/fesm2022/ngwr-loading-bar.mjs.map +1 -1
- package/fesm2022/ngwr-markdown.mjs +63 -11
- package/fesm2022/ngwr-markdown.mjs.map +1 -1
- package/fesm2022/ngwr-mention.mjs +104 -14
- package/fesm2022/ngwr-mention.mjs.map +1 -1
- package/fesm2022/ngwr-overlay.mjs +36 -7
- package/fesm2022/ngwr-overlay.mjs.map +1 -1
- package/fesm2022/ngwr-pagination-testing.mjs +11 -10
- package/fesm2022/ngwr-pagination-testing.mjs.map +1 -1
- package/fesm2022/ngwr-pagination.mjs +65 -30
- package/fesm2022/ngwr-pagination.mjs.map +1 -1
- package/fesm2022/ngwr-popconfirm.mjs +3 -2
- package/fesm2022/ngwr-popconfirm.mjs.map +1 -1
- package/fesm2022/ngwr-popover.mjs +2 -1
- package/fesm2022/ngwr-popover.mjs.map +1 -1
- package/fesm2022/ngwr-qr.mjs +38 -1
- package/fesm2022/ngwr-qr.mjs.map +1 -1
- package/fesm2022/ngwr-select.mjs +93 -34
- package/fesm2022/ngwr-select.mjs.map +1 -1
- package/fesm2022/ngwr-shiny-text.mjs +10 -3
- package/fesm2022/ngwr-shiny-text.mjs.map +1 -1
- package/fesm2022/ngwr-splitter-testing.mjs.map +1 -1
- package/fesm2022/ngwr-squircle.mjs +2 -1
- package/fesm2022/ngwr-squircle.mjs.map +1 -1
- package/fesm2022/ngwr-statistic.mjs +35 -4
- package/fesm2022/ngwr-statistic.mjs.map +1 -1
- package/fesm2022/ngwr-table.mjs +35 -12
- package/fesm2022/ngwr-table.mjs.map +1 -1
- package/fesm2022/ngwr-tabs-router.mjs +158 -0
- package/fesm2022/ngwr-tabs-router.mjs.map +1 -0
- package/fesm2022/ngwr-tabs-testing.mjs +13 -11
- package/fesm2022/ngwr-tabs-testing.mjs.map +1 -1
- package/fesm2022/ngwr-tabs.mjs +104 -12
- package/fesm2022/ngwr-tabs.mjs.map +1 -1
- package/fesm2022/ngwr-textarea.mjs +20 -1
- package/fesm2022/ngwr-textarea.mjs.map +1 -1
- package/fesm2022/ngwr-tour.mjs +1 -1
- package/fesm2022/ngwr-tour.mjs.map +1 -1
- package/fesm2022/ngwr-transfer.mjs +2 -2
- package/fesm2022/ngwr-transfer.mjs.map +1 -1
- package/fesm2022/ngwr-tree.mjs +18 -2
- package/fesm2022/ngwr-tree.mjs.map +1 -1
- package/fesm2022/ngwr-utils.mjs +143 -1
- package/fesm2022/ngwr-utils.mjs.map +1 -1
- package/fesm2022/ngwr-version.mjs +1 -1
- package/fesm2022/ngwr-version.mjs.map +1 -1
- package/fesm2022/ngwr-window-testing.mjs +10 -2
- package/fesm2022/ngwr-window-testing.mjs.map +1 -1
- package/fesm2022/ngwr-window.mjs +4 -4
- package/fesm2022/ngwr-window.mjs.map +1 -1
- package/i18n/en.json +33 -6
- package/i18n/ru.json +33 -6
- package/llms-full.txt +20 -11
- package/llms.txt +47 -16
- package/mcp/catalog.js +3 -3
- package/mcp/providers.js +11 -0
- package/package.json +11 -3
- package/schematics/migrations/v13/index.js +101 -0
- package/schematics/migrations/v14/index.js +422 -0
- package/schematics/migrations.json +10 -0
- package/schematics/page/templates.js +1 -1
- package/schematics/use/symbol-map.json +1 -0
- package/skills/ngwr/SKILL.md +4 -1
- package/skills/ngwr/references/catalog.md +2 -0
- package/skills/ngwr/references/setup.md +4 -0
- package/stepper/styles/_index.scss +19 -1
- package/textarea/styles/_index.scss +14 -6
- package/toast/styles/_index.scss +12 -4
- package/types/ngwr-alert.d.ts +3 -3
- package/types/ngwr-blur-text.d.ts +8 -0
- package/types/ngwr-breadcrumbs.d.ts +1 -0
- package/types/ngwr-button.d.ts +2 -2
- package/types/ngwr-calendar-testing.d.ts +21 -4
- package/types/ngwr-calendar.d.ts +37 -0
- package/types/ngwr-circular-text.d.ts +9 -0
- package/types/ngwr-collapse.d.ts +19 -15
- package/types/ngwr-color-picker-testing.d.ts +10 -4
- package/types/ngwr-color-picker.d.ts +70 -2
- package/types/ngwr-command-palette.d.ts +14 -0
- package/types/ngwr-context-menu.d.ts +1 -1
- package/types/ngwr-date-adapters-fns.d.ts +71 -29
- package/types/ngwr-date-picker-testing.d.ts +206 -185
- package/types/ngwr-date-picker.d.ts +7 -0
- package/types/ngwr-date.d.ts +143 -13
- package/types/ngwr-drag-drop.d.ts +19 -0
- package/types/ngwr-dropdown.d.ts +1 -1
- package/types/ngwr-event-calendar-testing.d.ts +12 -9
- package/types/ngwr-file-upload.d.ts +31 -3
- package/types/ngwr-fuzzy-text.d.ts +5 -0
- package/types/ngwr-i18n.d.ts +89 -7
- package/types/ngwr-icon-adapters-feather.d.ts +11 -0
- package/types/ngwr-icon.d.ts +42 -1
- package/types/ngwr-input-number.d.ts +1 -1
- package/types/ngwr-input-otp.d.ts +3 -0
- package/types/ngwr-input.d.ts +11 -4
- package/types/ngwr-knob-testing.d.ts +8 -4
- package/types/ngwr-knob.d.ts +18 -0
- package/types/ngwr-loading-bar-router.d.ts +35 -0
- package/types/ngwr-loading-bar.d.ts +19 -9
- package/types/ngwr-mention.d.ts +13 -0
- package/types/ngwr-pagination-testing.d.ts +11 -10
- package/types/ngwr-pagination.d.ts +39 -10
- package/types/ngwr-popconfirm.d.ts +1 -1
- package/types/ngwr-popover.d.ts +1 -1
- package/types/ngwr-select.d.ts +65 -10
- package/types/ngwr-shiny-text.d.ts +1 -0
- package/types/ngwr-splitter-testing.d.ts +9 -4
- package/types/ngwr-squircle.d.ts +1 -1
- package/types/ngwr-statistic.d.ts +22 -2
- package/types/ngwr-table.d.ts +24 -5
- package/types/ngwr-tabs-router.d.ts +67 -0
- package/types/ngwr-tabs-testing.d.ts +12 -10
- package/types/ngwr-tabs.d.ts +103 -6
- package/types/ngwr-textarea.d.ts +9 -0
- package/types/ngwr-tree.d.ts +7 -0
- package/types/ngwr-utils.d.ts +79 -1
- package/types/ngwr-version.d.ts +1 -1
- package/types/ngwr-window-testing.d.ts +11 -3
- package/types/ngwr-window.d.ts +11 -4
- package/window/styles/_index.scss +1 -1
|
@@ -6,6 +6,7 @@ import * as i0 from '@angular/core';
|
|
|
6
6
|
import { input, output, ViewEncapsulation, Component, inject, ElementRef, DestroyRef, PLATFORM_ID, signal, computed, Directive } from '@angular/core';
|
|
7
7
|
import { readI18nText, useI18nFormatter } from 'ngwr/i18n';
|
|
8
8
|
import { WR_OVERLAY } from 'ngwr/overlay';
|
|
9
|
+
import { isComposing } from 'ngwr/utils';
|
|
9
10
|
|
|
10
11
|
/**
|
|
11
12
|
* @license
|
|
@@ -23,6 +24,13 @@ import { WR_OVERLAY } from 'ngwr/overlay';
|
|
|
23
24
|
*/
|
|
24
25
|
const MIRROR_PROPS = [
|
|
25
26
|
'boxSizing',
|
|
27
|
+
// Without this the mirror inherits `<body>`'s direction, and the two disagree
|
|
28
|
+
// whenever the field carries its own `dir` — measured at 230px of error for a
|
|
29
|
+
// 320px RTL field inside an LTR page, which put the panel outside the field
|
|
30
|
+
// entirely. `textAlign` below is the same story from the other side: an RTL
|
|
31
|
+
// field's computed value is the logical `start`, which only resolves to the
|
|
32
|
+
// right edge once the mirror knows the direction.
|
|
33
|
+
'direction',
|
|
26
34
|
'width',
|
|
27
35
|
'height',
|
|
28
36
|
'overflowX',
|
|
@@ -53,14 +61,55 @@ const MIRROR_PROPS = [
|
|
|
53
61
|
'whiteSpace',
|
|
54
62
|
'wordWrap',
|
|
55
63
|
];
|
|
64
|
+
/** A caret has no size, and an environment with no layout has no rects at all. */
|
|
65
|
+
const NO_RECT = { top: 0, left: 0 };
|
|
66
|
+
/**
|
|
67
|
+
* Where the browser would draw the caret this collapsed `range` marks.
|
|
68
|
+
*
|
|
69
|
+
* `getClientRects()` before `getBoundingClientRect()`: a collapsed range at a
|
|
70
|
+
* bidi boundary has TWO candidate positions and reports both, and the bounding
|
|
71
|
+
* box of the pair spans the whole run between them. The first rect is the one
|
|
72
|
+
* the caret is actually painted at.
|
|
73
|
+
*
|
|
74
|
+
* Both are optional-called because jsdom implements NEITHER on `Range` — it lays
|
|
75
|
+
* nothing out, so every element rect in it is already 0×0 and a zero here keeps
|
|
76
|
+
* the function answering what the rest of that environment answers rather than
|
|
77
|
+
* throwing halfway through an unrelated spec.
|
|
78
|
+
*/
|
|
79
|
+
function caretRectOf(range) {
|
|
80
|
+
const rects = range.getClientRects?.();
|
|
81
|
+
if (rects && rects.length > 0)
|
|
82
|
+
return rects[0];
|
|
83
|
+
return range.getBoundingClientRect?.() ?? NO_RECT;
|
|
84
|
+
}
|
|
56
85
|
/**
|
|
57
86
|
* Compute the viewport coordinates of a textarea / input caret at a given
|
|
58
|
-
* `position`. Uses the "mirror div" technique — a hidden div with the
|
|
59
|
-
*
|
|
60
|
-
*
|
|
87
|
+
* `position`. Uses the "mirror div" technique — a hidden div with the same
|
|
88
|
+
* styling holds the field's text, and a COLLAPSED RANGE at `position` reports
|
|
89
|
+
* where the caret would be.
|
|
61
90
|
*
|
|
62
91
|
* Returns viewport-relative `{ top, left }` — pass straight to a CDK
|
|
63
92
|
* Overlay anchored at that point.
|
|
93
|
+
*
|
|
94
|
+
* **A range, not a probe span, and the difference only shows in RTL.** The
|
|
95
|
+
* classic version of this technique puts the text before the caret in the div,
|
|
96
|
+
* the text after it in a `<span>`, and reads the span's LEFT edge. That reads
|
|
97
|
+
* the caret's side of the boundary only while the text runs left to right. Under
|
|
98
|
+
* the Unicode BiDi algorithm the span can be laid out to the LEFT of the run it
|
|
99
|
+
* follows, so the edge that faces the caret is its right one — and which edge
|
|
100
|
+
* that is depends on the bidi level of the text at the caret, not on the field's
|
|
101
|
+
* direction, so there is no side to pick. A collapsed range asks the browser
|
|
102
|
+
* where it would draw the caret and gets the reordering for free. Measured
|
|
103
|
+
* against a `contenteditable` twin of the field, in Chromium: the range agrees
|
|
104
|
+
* to within a pixel for Latin, Arabic and mixed text in both directions, where
|
|
105
|
+
* the span was out by up to 300px in RTL (see the spec next door).
|
|
106
|
+
*
|
|
107
|
+
* The `<input>` branch keeps the field's WIDTH for the same reason. It used to
|
|
108
|
+
* force `width: auto` so a long single line could not wrap; but an RTL field
|
|
109
|
+
* right-aligns its text inside its own box, so a shrink-to-fit mirror throws
|
|
110
|
+
* away the only measurement that decides where the text starts. `overflow:
|
|
111
|
+
* hidden` is what keeps the overflow from painting, and `input.scrollLeft`
|
|
112
|
+
* already accounts for a field scrolled past its box.
|
|
64
113
|
*/
|
|
65
114
|
function getCaretCoordinates(input, position) {
|
|
66
115
|
const doc = input.ownerDocument;
|
|
@@ -79,24 +128,24 @@ function getCaretCoordinates(input, position) {
|
|
|
79
128
|
if (input.tagName === 'INPUT') {
|
|
80
129
|
mirror.style.whiteSpace = 'pre';
|
|
81
130
|
mirror.style.height = 'auto';
|
|
82
|
-
mirror.style.width = 'auto';
|
|
83
131
|
mirror.style.overflow = 'hidden';
|
|
84
132
|
}
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
const
|
|
88
|
-
|
|
89
|
-
probe.textContent = input.value.substring(position) || '.';
|
|
90
|
-
mirror.appendChild(probe);
|
|
133
|
+
// One text node, so the range can address any offset in it. An empty field
|
|
134
|
+
// still needs a line box to measure, hence the fallback character.
|
|
135
|
+
const text = doc.createTextNode(input.value || '.');
|
|
136
|
+
mirror.appendChild(text);
|
|
91
137
|
doc.body.appendChild(mirror);
|
|
92
|
-
const
|
|
138
|
+
const range = doc.createRange();
|
|
139
|
+
range.setStart(text, Math.min(position, text.length));
|
|
140
|
+
range.collapse(true);
|
|
141
|
+
const caretRect = caretRectOf(range);
|
|
93
142
|
const mirrorRect = mirror.getBoundingClientRect();
|
|
94
143
|
const lineHeight = parseFloat(style.lineHeight) || parseFloat(style.fontSize) * 1.2;
|
|
95
144
|
doc.body.removeChild(mirror);
|
|
96
145
|
const inputRect = input.getBoundingClientRect();
|
|
97
146
|
return {
|
|
98
|
-
top: inputRect.top + (
|
|
99
|
-
left: inputRect.left + (
|
|
147
|
+
top: inputRect.top + (caretRect.top - mirrorRect.top) - input.scrollTop,
|
|
148
|
+
left: inputRect.left + (caretRect.left - mirrorRect.left) - input.scrollLeft,
|
|
100
149
|
lineHeight,
|
|
101
150
|
};
|
|
102
151
|
}
|
|
@@ -314,8 +363,33 @@ class WrMention {
|
|
|
314
363
|
this.liveRegion.textContent = message;
|
|
315
364
|
}
|
|
316
365
|
// Host listeners
|
|
366
|
+
/**
|
|
367
|
+
* True between `compositionstart` and `compositionend`.
|
|
368
|
+
*
|
|
369
|
+
* Detection must not run on the half-built text an IME leaves in the field:
|
|
370
|
+
* `@やま` is a reading on its way to `@山田`, and matching the reading opens a
|
|
371
|
+
* panel that then eats the Enter meant to accept the conversion. So the field
|
|
372
|
+
* is read once, on `compositionend`, when the text is real.
|
|
373
|
+
*/
|
|
374
|
+
composing = false;
|
|
375
|
+
/** @internal */
|
|
376
|
+
onCompositionStart() {
|
|
377
|
+
this.composing = true;
|
|
378
|
+
// A panel opened before the conversion started would take the arrows and the
|
|
379
|
+
// Enter that now belong to the candidate window.
|
|
380
|
+
this.close();
|
|
381
|
+
}
|
|
382
|
+
/** @internal */
|
|
383
|
+
onCompositionEnd() {
|
|
384
|
+
this.composing = false;
|
|
385
|
+
// The committed text is only in the field now, so this is the first honest
|
|
386
|
+
// read of it.
|
|
387
|
+
this.detect();
|
|
388
|
+
}
|
|
317
389
|
/** @internal */
|
|
318
390
|
onInput() {
|
|
391
|
+
if (this.composing)
|
|
392
|
+
return;
|
|
319
393
|
this.detect();
|
|
320
394
|
}
|
|
321
395
|
/** @internal */
|
|
@@ -324,6 +398,8 @@ class WrMention {
|
|
|
324
398
|
}
|
|
325
399
|
/** @internal */
|
|
326
400
|
onCaretMove(event) {
|
|
401
|
+
if (this.composing || isComposing(event))
|
|
402
|
+
return;
|
|
327
403
|
// Every key `onKeydown` already consumed, Escape included: its keyup would
|
|
328
404
|
// re-detect at a caret that is still sitting inside the mention and reopen the
|
|
329
405
|
// panel Escape had just dismissed. A synthetic keydown-only Escape hides that
|
|
@@ -339,6 +415,13 @@ class WrMention {
|
|
|
339
415
|
}
|
|
340
416
|
/** @internal */
|
|
341
417
|
onKeydown(event) {
|
|
418
|
+
// The key is the input method's while a conversion is open — Enter accepts a
|
|
419
|
+
// candidate, Escape cancels the reading, the arrows walk the candidate list.
|
|
420
|
+
// `this.composing` is deliberately not consulted: on Safari's committing
|
|
421
|
+
// keystroke it has already gone false, and `isComposing` covers that case
|
|
422
|
+
// through the 229 sentinel.
|
|
423
|
+
if (isComposing(event))
|
|
424
|
+
return;
|
|
342
425
|
if (!this.state)
|
|
343
426
|
return;
|
|
344
427
|
const list = this.filteredItems();
|
|
@@ -584,7 +667,7 @@ class WrMention {
|
|
|
584
667
|
this.announce(this.insertedText({ label }));
|
|
585
668
|
}
|
|
586
669
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: WrMention, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
|
587
|
-
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "22.1.4", type: WrMention, isStandalone: true, selector: "[wrMention]", inputs: { wrMentionItems: { classPropertyName: "wrMentionItems", publicName: "wrMentionItems", isSignal: true, isRequired: false, transformFunction: null }, triggers: { classPropertyName: "triggers", publicName: "triggers", isSignal: true, isRequired: false, transformFunction: null }, displayWith: { classPropertyName: "displayWith", publicName: "displayWith", isSignal: true, isRequired: false, transformFunction: null }, valueWith: { classPropertyName: "valueWith", publicName: "valueWith", isSignal: true, isRequired: false, transformFunction: null }, filterWith: { classPropertyName: "filterWith", publicName: "filterWith", isSignal: true, isRequired: false, transformFunction: null }, maxResults: { classPropertyName: "maxResults", publicName: "maxResults", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { wrMentionSelected: "wrMentionSelected" }, host: { attributes: { "aria-autocomplete": "list", "aria-haspopup": "listbox" }, listeners: { "input": "onInput()", "click": "onClick()", "keyup": "onCaretMove($event)", "keydown": "onKeydown($event)", "blur": "onBlur()" }, properties: { "attr.aria-controls": "listboxId", "attr.aria-activedescendant": "activeOptionId()" } }, ngImport: i0 });
|
|
670
|
+
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "22.1.4", type: WrMention, isStandalone: true, selector: "[wrMention]", inputs: { wrMentionItems: { classPropertyName: "wrMentionItems", publicName: "wrMentionItems", isSignal: true, isRequired: false, transformFunction: null }, triggers: { classPropertyName: "triggers", publicName: "triggers", isSignal: true, isRequired: false, transformFunction: null }, displayWith: { classPropertyName: "displayWith", publicName: "displayWith", isSignal: true, isRequired: false, transformFunction: null }, valueWith: { classPropertyName: "valueWith", publicName: "valueWith", isSignal: true, isRequired: false, transformFunction: null }, filterWith: { classPropertyName: "filterWith", publicName: "filterWith", isSignal: true, isRequired: false, transformFunction: null }, maxResults: { classPropertyName: "maxResults", publicName: "maxResults", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { wrMentionSelected: "wrMentionSelected" }, host: { attributes: { "aria-autocomplete": "list", "aria-haspopup": "listbox" }, listeners: { "input": "onInput()", "click": "onClick()", "keyup": "onCaretMove($event)", "keydown": "onKeydown($event)", "blur": "onBlur()", "compositionstart": "onCompositionStart()", "compositionend": "onCompositionEnd()" }, properties: { "attr.aria-controls": "listboxId", "attr.aria-activedescendant": "activeOptionId()" } }, ngImport: i0 });
|
|
588
671
|
}
|
|
589
672
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: WrMention, decorators: [{
|
|
590
673
|
type: Directive,
|
|
@@ -596,6 +679,13 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.4", ngImpor
|
|
|
596
679
|
'(keyup)': 'onCaretMove($event)',
|
|
597
680
|
'(keydown)': 'onKeydown($event)',
|
|
598
681
|
'(blur)': 'onBlur()',
|
|
682
|
+
// The IME pair. `isComposing` on a `keydown` answers "did the input method
|
|
683
|
+
// take this key", which is a different question from "is a conversion open
|
|
684
|
+
// right now" — on the committing keystroke the two disagree by design — and
|
|
685
|
+
// detection runs from `input` and `keyup`, where there is no such flag to
|
|
686
|
+
// read. So the state is tracked here and the detector consults it.
|
|
687
|
+
'(compositionstart)': 'onCompositionStart()',
|
|
688
|
+
'(compositionend)': 'onCompositionEnd()',
|
|
599
689
|
// The host keeps its implicit `role="textbox"`. It is NOT a combobox: this
|
|
600
690
|
// field holds prose and the mention is one fragment inside it, not the
|
|
601
691
|
// field's value. `role="combobox"` is also flatly disallowed on `<textarea>`
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ngwr-mention.mjs","sources":["../../../projects/lib/mention/caret.ts","../../../projects/lib/mention/mention-panel.ts","../../../projects/lib/mention/mention-panel.html","../../../projects/lib/mention/mention.ts","../../../projects/lib/mention/ngwr-mention.ts"],"sourcesContent":["/**\n * @license\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://github.com/thekhegay/ngwr/blob/main/LICENSE\n */\n\n/**\n * CSS properties whose values must be copied onto the mirror to match layout.\n *\n * `as const satisfies` keeps each name checked against `CSSStyleDeclaration`\n * while narrowing the element type to these string-valued literals — the wider\n * `keyof CSSStyleDeclaration` also spans its methods (`setProperty`, `item`, …),\n * which makes the indexed read below look like an unbound method.\n */\nconst MIRROR_PROPS = [\n 'boxSizing',\n 'width',\n 'height',\n 'overflowX',\n 'overflowY',\n 'borderTopWidth',\n 'borderRightWidth',\n 'borderBottomWidth',\n 'borderLeftWidth',\n 'paddingTop',\n 'paddingRight',\n 'paddingBottom',\n 'paddingLeft',\n 'fontStyle',\n 'fontVariant',\n 'fontWeight',\n 'fontStretch',\n 'fontSize',\n 'fontSizeAdjust',\n 'lineHeight',\n 'fontFamily',\n 'textAlign',\n 'textTransform',\n 'textIndent',\n 'textDecoration',\n 'letterSpacing',\n 'wordSpacing',\n 'tabSize',\n 'whiteSpace',\n 'wordWrap',\n] as const satisfies readonly (keyof CSSStyleDeclaration)[];\n\n/**\n * Compute the viewport coordinates of a textarea / input caret at a given\n * `position`. Uses the \"mirror div\" technique — a hidden div with the\n * same styling holds the text up to the caret, and we read the bounding\n * rect of a probe span placed at the boundary.\n *\n * Returns viewport-relative `{ top, left }` — pass straight to a CDK\n * Overlay anchored at that point.\n */\nexport function getCaretCoordinates(\n input: HTMLTextAreaElement | HTMLInputElement,\n position: number\n): { top: number; left: number; lineHeight: number } {\n const doc = input.ownerDocument;\n const style = getComputedStyle(input);\n const mirror = doc.createElement('div');\n\n for (const prop of MIRROR_PROPS) {\n mirror.style[prop] = style[prop];\n }\n mirror.style.position = 'absolute';\n mirror.style.visibility = 'hidden';\n mirror.style.top = '0';\n mirror.style.left = '0';\n mirror.style.whiteSpace = 'pre-wrap';\n mirror.style.wordWrap = 'break-word';\n\n // <input> is single-line — force whitespace behaviour so coords are sane.\n if (input.tagName === 'INPUT') {\n mirror.style.whiteSpace = 'pre';\n mirror.style.height = 'auto';\n mirror.style.width = 'auto';\n mirror.style.overflow = 'hidden';\n }\n\n const before = input.value.substring(0, position);\n mirror.textContent = before;\n\n const probe = doc.createElement('span');\n // Single character so the span has measurable size even at the end.\n probe.textContent = input.value.substring(position) || '.';\n mirror.appendChild(probe);\n\n doc.body.appendChild(mirror);\n const probeRect = probe.getBoundingClientRect();\n const mirrorRect = mirror.getBoundingClientRect();\n const lineHeight = parseFloat(style.lineHeight) || parseFloat(style.fontSize) * 1.2;\n doc.body.removeChild(mirror);\n\n const inputRect = input.getBoundingClientRect();\n return {\n top: inputRect.top + (probeRect.top - mirrorRect.top) - input.scrollTop,\n left: inputRect.left + (probeRect.left - mirrorRect.left) - input.scrollLeft,\n lineHeight,\n };\n}\n","/**\n * @license\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://github.com/thekhegay/ngwr/blob/main/LICENSE\n */\n\nimport { Component, ViewEncapsulation, input, output } from '@angular/core';\n\nimport type { WrMentionItem } from './interfaces';\n\n/**\n * The id of the option at `index`, given the listbox's own id.\n *\n * Lives here, and is used from both sides, because this format is a contract\n * between two files: the panel stamps it onto each `<li>`, and the directive\n * names one of them in `aria-activedescendant`. Written out twice, the two would\n * drift and the reference would silently dangle — and a dangling\n * `aria-activedescendant` is not an error the browser reports, it just stops\n * announcing options.\n *\n * @internal\n */\nexport function wrMentionOptionId(listboxId: string, index: number): string {\n return `${listboxId}-opt-${index}`;\n}\n\n/**\n * Overlay panel rendered by {@link WrMention}. Not intended for\n * direct use — the directive instantiates it via a ComponentPortal.\n *\n * @internal — the same tag its twin `WrPopconfirmPanel` already carried. It says\n * the intent; it does not by itself keep the class out of the generated API\n * tables, because `WrTableFilter` and `WrTableSort` are tagged AND documented —\n * see `EXPORTED_BUT_INTERNAL` in `scripts/lib/extract-api.ts`.\n */\n@Component({\n selector: 'wr-mention-panel',\n templateUrl: './mention-panel.html',\n encapsulation: ViewEncapsulation.None,\n host: { class: 'wr-mention-panel' },\n})\nexport class WrMentionPanel {\n readonly items = input<readonly WrMentionItem[]>([]);\n readonly activeIndex = input(0);\n readonly displayWith = input<(item: WrMentionItem) => string>(item => item.label);\n\n /**\n * Id of the `<ul role=\"listbox\">`, minted by the directive and pointed at by\n * its `aria-controls`. Owned there rather than here because the panel is\n * disposed and rebuilt whenever the caret leaves a mention, while the\n * attribute on the host has to stay put.\n *\n * Plain `input('')`, never `input.required` — a ComponentPortal-created\n * component throws on the first change detection if a required input is unset.\n */\n readonly listboxId = input('');\n\n /** Accessible name for the listbox. ARIA requires one; the directive supplies it from the catalog. */\n readonly listLabel = input('');\n\n protected optionId(index: number): string {\n return wrMentionOptionId(this.listboxId(), index);\n }\n\n /** Emitted when the user picks an item (click). */\n readonly picked = output<WrMentionItem>();\n\n /** Emitted on mouseenter — used by the directive to track hover. */\n readonly hovered = output<number>();\n\n protected onPick(item: WrMentionItem, event: MouseEvent): void {\n // mousedown semantics — keep focus on the input.\n event.preventDefault();\n this.picked.emit(item);\n }\n}\n","<!--\n No empty branch: `activate()` calls `close()` and returns before `open()` when\n nothing matches, so the panel is never rendered with zero items. The \"No\n matches\" `<div>` that used to live here was unreachable — and a hard-coded\n English string besides. The zero-match case is announced through the\n directive's live region instead.\n-->\n<ul class=\"wr-mention-panel__list\" role=\"listbox\" [attr.id]=\"listboxId()\" [attr.aria-label]=\"listLabel()\">\n @for (item of items(); track $index) {\n <li\n class=\"wr-mention-panel__option\"\n role=\"option\"\n [attr.id]=\"optionId($index)\"\n [class.wr-mention-panel__option--active]=\"$index === activeIndex()\"\n [attr.aria-selected]=\"$index === activeIndex()\"\n (mouseenter)=\"hovered.emit($index)\"\n (mousedown)=\"onPick(item, $event)\"\n >\n {{ displayWith()(item) }}\n </li>\n }\n</ul>\n","/**\n * @license\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://github.com/thekhegay/ngwr/blob/main/LICENSE\n */\n\nimport { coerceNumberProperty } from '@angular/cdk/coercion';\nimport {\n type ConnectedPosition,\n type FlexibleConnectedPositionStrategyOrigin,\n type OverlayRef,\n ScrollStrategyOptions,\n} from '@angular/cdk/overlay';\nimport { ComponentPortal } from '@angular/cdk/portal';\nimport { isPlatformBrowser } from '@angular/common';\nimport {\n type ComponentRef,\n DestroyRef,\n Directive,\n ElementRef,\n PLATFORM_ID,\n computed,\n inject,\n input,\n output,\n signal,\n} from '@angular/core';\n\nimport { readI18nText, useI18nFormatter } from 'ngwr/i18n';\nimport { WR_OVERLAY } from 'ngwr/overlay';\n\nimport { getCaretCoordinates } from './caret';\nimport type { WrMentionCommit, WrMentionItem } from './interfaces';\nimport { WrMentionPanel, wrMentionOptionId } from './mention-panel';\n\ninterface ActiveState {\n trigger: string;\n query: string;\n startIndex: number;\n}\n\n/**\n * Deterministic under prerender, unlike `randomId()` — `aria-controls` is a\n * static host binding, so it renders on the server too, and a random value would\n * differ between that render and the hydrated client.\n */\nlet mentionUid = 0;\n\n/** Gap between the caret's line and the panel, on whichever side it opens. */\nconst CARET_GAP = 4;\n\n/**\n * Below the caret's line first, above it when there is no room — the two sides\n * a caret-anchored list can take. There is no horizontal pair: a panel that\n * jumped to the other side of the caret would read as a different widget, so a\n * caret near the inline end is handled by `withPush` sliding the panel back\n * onto the screen instead. `overlayX: 'start'` is the logical corner, so RTL\n * hangs the panel from the caret's other side without a second table.\n */\nconst CARET_POSITIONS: ConnectedPosition[] = [\n { originX: 'start', originY: 'bottom', overlayX: 'start', overlayY: 'top' },\n { originX: 'start', originY: 'top', overlayX: 'start', overlayY: 'bottom' },\n];\n\n/**\n * `@`-style mention picker for `<textarea>` and `<input>`. Listens for\n * trigger characters (default `'@'`); when typed at a word boundary, an\n * overlay panel opens at the caret and shows filtered options. Pick with\n * Enter / Tab / click — the trigger + query is replaced with the inserted\n * value plus a trailing space.\n *\n * @example\n * ```html\n * <textarea\n * wrMention\n * [wrMentionItems]=\"users\"\n * [displayWith]=\"userLabel\"\n * (wrMentionSelected)=\"onMention($event)\"\n * ></textarea>\n * ```\n *\n * @see https://ngwr.dev/reference/components/mention\n */\n@Directive({\n selector: '[wrMention]',\n host: {\n '(input)': 'onInput()',\n '(click)': 'onClick()',\n '(keyup)': 'onCaretMove($event)',\n '(keydown)': 'onKeydown($event)',\n '(blur)': 'onBlur()',\n\n // The host keeps its implicit `role=\"textbox\"`. It is NOT a combobox: this\n // field holds prose and the mention is one fragment inside it, not the\n // field's value. `role=\"combobox\"` is also flatly disallowed on `<textarea>`\n // by ARIA in HTML (axe: `aria-allowed-role`), and it would drop\n // `aria-multiline`, which combobox does not support — so a screen reader\n // would stop reporting the field as multi-line for the whole editing\n // session, not just while the popup is open.\n //\n // Static, because `aria-autocomplete` describes a permanent capability of\n // the field; ARIA says authors SHOULD NOT toggle it to signal that\n // suggestions are currently showing. Being static is also what makes the\n // feature discoverable — it is announced on focus, before the user types a\n // trigger char.\n 'aria-autocomplete': 'list',\n // Required alongside `aria-autocomplete=\"list\"`, and must name the popup's\n // role. Measured clean on a textbox with axe-core 4.13.\n 'aria-haspopup': 'listbox',\n // Also static, and deliberately left pointing at an id that only exists\n // while the panel is open: gating it would mean gating `aria-autocomplete`\n // too. An unresolved `aria-controls` is a manual-review note in axe, never a\n // violation — unlike `aria-activedescendant`, which is an author error.\n //\n // This is the attribute that legalises the whole design: for a textbox\n // controlling a popup, `aria-activedescendant` is allowed to name an element\n // owned by the *controlled* element, which is how the reference reaches into\n // the CDK overlay's separate DOM subtree at all.\n '[attr.aria-controls]': 'listboxId',\n '[attr.aria-activedescendant]': 'activeOptionId()',\n\n // No `aria-expanded`. It is not a supported state of `role=\"textbox\"` —\n // axe rates it a *critical* `aria-allowed-attr` violation — so the\n // \"a list appeared\" signal cannot be an ARIA state here. That is what the\n // live region below is for; it is load-bearing, not decoration.\n },\n})\nexport class WrMention<T extends WrMentionItem = WrMentionItem> {\n /** Items to filter against the typed query. */\n readonly wrMentionItems = input<readonly T[]>([]);\n\n /** Trigger characters that open the panel. @default ['@'] */\n readonly triggers = input<readonly string[]>(['@']);\n\n /** Maps an item to its display label. @default `item.label` */\n readonly displayWith = input<(item: T) => string>((item: T) => item.label);\n\n /**\n * Returns the text to insert into the textarea on commit. Receives the\n * picked item and the trigger char. @default `${trigger}${displayWith(item)}`\n */\n readonly valueWith = input<((item: T, trigger: string) => string) | null>(null);\n\n /** Custom filter. @default case-insensitive `includes` over `displayWith(item)`. */\n readonly filterWith = input<((query: string, item: T) => boolean) | null>(null);\n\n /** Maximum number of items shown in the panel. @default 8 */\n readonly maxResults = input(8, { transform: (v: unknown): number => Math.max(1, coerceNumberProperty(v, 8)) });\n\n /** Emits the selected item, trigger, and query whenever the user commits. */\n readonly wrMentionSelected = output<WrMentionCommit<T>>();\n\n private readonly host = inject<ElementRef<HTMLTextAreaElement | HTMLInputElement>>(ElementRef);\n private readonly overlay = inject(WR_OVERLAY);\n private readonly scrollStrategies = inject(ScrollStrategyOptions);\n private readonly destroyRef = inject(DestroyRef);\n private readonly isBrowser = isPlatformBrowser(inject(PLATFORM_ID));\n\n private overlayRef: OverlayRef | null = null;\n private panelRef: ComponentRef<WrMentionPanel> | null = null;\n /**\n * Where the caret sits INSIDE the field — its offset from the field's\n * top-left corner, plus the height of the line it is on. Mutated in place by\n * `open()`, never replaced: `caretAnchor()` closes over this object.\n */\n private readonly caretOffset = { dx: 0, dy: 0, lineHeight: 0 };\n private state: ActiveState | null = null;\n private readonly activeIndex = signal(0);\n private readonly filteredItems = signal<readonly T[]>([]);\n private readonly isOpen = signal(false);\n\n /** @internal — read by the `aria-controls` host binding. */\n protected readonly listboxId = `wr-mention-listbox-${++mentionUid}`;\n\n private readonly listLabel = readI18nText('mention.listbox', 'Mentions');\n private readonly availableText = useI18nFormatter('mention.available', 'Matches available: {{count}}');\n private readonly insertedText = useI18nFormatter('mention.inserted', 'Inserted: {{label}}');\n\n /**\n * `aria-activedescendant`, or `null`.\n *\n * Gated hard on `isOpen()`, which is only set once the panel's options are\n * actually in the DOM. An id that resolves to nothing is an author error, and\n * the failure mode is silent: the browser does not complain, it simply stops\n * firing the event that makes a screen reader speak the option. Same\n * discipline `tree.ts` uses for its virtualized rows.\n */\n protected readonly activeOptionId = computed<string | null>(() => {\n if (!this.isOpen()) return null;\n const index = this.activeIndex();\n return index >= 0 && index < this.filteredItems().length ? wrMentionOptionId(this.listboxId, index) : null;\n });\n\n /**\n * Polite live region, parked on `<body>`.\n *\n * Not inside the panel: `close()` disposes the overlay on every keystroke that\n * leaves a mention, so a region living there would be recreated constantly —\n * and a live region has to be in the DOM *before* its text changes to be\n * announced at all. It also could not announce the two cases that matter most,\n * since in both the panel is already gone: \"nothing matched\" and \"inserted X\".\n *\n * Not a sibling of the host either — that would land inside the consumer's\n * markup and start matching their `+`, `~` and `:nth-child()` selectors.\n */\n private liveRegion: HTMLElement | null = null;\n\n constructor() {\n if (this.isBrowser) {\n const doc = this.host.nativeElement.ownerDocument;\n const region = doc.createElement('div');\n region.className = 'wr-mention__status';\n region.setAttribute('role', 'status');\n doc.body.appendChild(region);\n this.liveRegion = region;\n }\n\n this.destroyRef.onDestroy(() => {\n this.close();\n this.liveRegion?.remove();\n this.liveRegion = null;\n });\n }\n\n private announce(message: string): void {\n if (this.liveRegion) this.liveRegion.textContent = message;\n }\n\n // Host listeners\n\n /** @internal */\n protected onInput(): void {\n this.detect();\n }\n\n /** @internal */\n protected onClick(): void {\n this.detect();\n }\n\n /** @internal */\n protected onCaretMove(event: KeyboardEvent): void {\n // Every key `onKeydown` already consumed, Escape included: its keyup would\n // re-detect at a caret that is still sitting inside the mention and reopen the\n // panel Escape had just dismissed. A synthetic keydown-only Escape hides that\n // — the real gesture is a pair, and the second half undid the first.\n if (\n event.key === 'ArrowUp' ||\n event.key === 'ArrowDown' ||\n event.key === 'Enter' ||\n event.key === 'Tab' ||\n event.key === 'Escape'\n ) {\n return;\n }\n this.detect();\n }\n\n /** @internal */\n protected onKeydown(event: KeyboardEvent): void {\n if (!this.state) return;\n const list = this.filteredItems();\n switch (event.key) {\n case 'ArrowDown':\n event.preventDefault();\n if (list.length > 0) this.setActive((this.activeIndex() + 1) % list.length);\n break;\n case 'ArrowUp':\n event.preventDefault();\n if (list.length > 0) this.setActive((this.activeIndex() - 1 + list.length) % list.length);\n break;\n case 'Enter':\n case 'Tab': {\n const item = list[this.activeIndex()];\n if (item) {\n event.preventDefault();\n this.commit(item);\n }\n break;\n }\n case 'Escape':\n event.preventDefault();\n // The panel is the innermost thing Escape can dismiss, so it keeps the\n // key: without this, the same press also closed the dialog / drawer /\n // window the field sits in.\n //\n // `preventDefault()` alone buys nothing — every host overlay matches on\n // `event.key` and none of them looks at `defaultPrevented` — and letting\n // CDK's `OverlayKeyboardDispatcher` sort it out does not work either.\n // The dispatcher walks the stack newest-first and stops at the first\n // overlay with a `keydownEvents()` subscriber; this panel has none (it\n // is driven from the field's own handler, which runs at the target\n // before the body-phase dispatcher), so Escape falls straight through to\n // the overlay underneath.\n event.stopPropagation();\n this.close();\n break;\n default:\n break;\n }\n }\n\n /** @internal */\n protected onBlur(): void {\n // Defer so option mousedown lands first.\n setTimeout(() => this.close(), 120);\n }\n\n // Detection\n\n private detect(): void {\n const el = this.host.nativeElement;\n const caret = el.selectionStart ?? 0;\n const text = el.value.substring(0, caret);\n const triggers = this.triggers();\n\n // Walk back from the caret looking for a trigger char with whitespace\n // (or start-of-string) immediately before it. Stop on whitespace.\n let i = caret;\n while (i > 0) {\n const ch = text[i - 1];\n if (triggers.includes(ch)) {\n const before = i - 2 >= 0 ? text[i - 2] : '';\n if (i - 2 < 0 || /\\s/.test(before)) {\n const query = text.substring(i);\n this.activate(ch, query, i - 1);\n return;\n }\n break;\n }\n if (/\\s/.test(ch)) break;\n i--;\n }\n this.close();\n }\n\n private activate(trigger: string, query: string, startIndex: number): void {\n this.state = { trigger, query, startIndex };\n const previousCount = this.filteredItems().length;\n const filtered = this.filter(query).slice(0, this.maxResults());\n this.filteredItems.set(filtered);\n if (filtered.length === 0) {\n // Announce *after* closing: `close()` clears the region, so announcing\n // first would wipe the message before a screen reader reached it.\n this.close();\n this.announce(this.availableText({ count: 0 }));\n return;\n }\n this.setActive(Math.min(this.activeIndex(), filtered.length - 1));\n this.open();\n // Only when the size actually changes. Re-announcing the same count on every\n // keystroke would talk over the per-option speech from activedescendant.\n if (filtered.length !== previousCount) this.announce(this.availableText({ count: filtered.length }));\n }\n\n private filter(query: string): readonly T[] {\n const items = this.wrMentionItems();\n const custom = this.filterWith();\n const display = this.displayWith();\n if (!query) return items;\n if (custom) return items.filter(item => custom(query, item));\n const q = query.toLowerCase();\n return items.filter(item => display(item).toLowerCase().includes(q));\n }\n\n private setActive(i: number): void {\n this.activeIndex.set(i);\n this.panelRef?.setInput('activeIndex', i);\n }\n\n // Overlay\n\n /**\n * The overlay's anchor: the caret's line box, grown by the gap the panel\n * keeps from the text, as a zero-WIDTH virtual origin in viewport\n * coordinates. Inflating the rect rather than passing `offsetY` is what keeps\n * `withPush` honest — CDK applies a position offset as a `transform` AFTER it\n * has clamped, so a pushed panel would land the offset off the screen edge.\n *\n * Live getters, not a snapshot, and that is the whole of the scroll fix.\n * `getCaretCoordinates` answers in viewport coordinates, which a\n * `GlobalPositionStrategy` froze at the instant of the keystroke: the panel\n * then stayed pinned to the screen while the page scrolled out from under it\n * (150px of scroll, 150px of drift, ending up over unrelated content). CDK\n * re-reads a virtual origin on every `apply()`, so pinning it to the FIELD's\n * live rect is what turns `reposition()` into a real re-anchor.\n *\n * Only the caret's offset inside the field is captured up front. Measuring it\n * runs the mirror-div pass in `caret.ts` — a full layout read, far too\n * expensive to repeat per scroll event — and it cannot change without a\n * keystroke, which re-enters `open()` anyway.\n */\n private caretAnchor(): FlexibleConnectedPositionStrategyOrigin {\n const field = this.host.nativeElement;\n const offset = this.caretOffset;\n return {\n width: 0,\n get x(): number {\n return field.getBoundingClientRect().left + offset.dx;\n },\n get y(): number {\n return field.getBoundingClientRect().top + offset.dy - CARET_GAP;\n },\n get height(): number {\n return offset.lineHeight + CARET_GAP * 2;\n },\n };\n }\n\n private open(): void {\n const el = this.host.nativeElement;\n const caret = el.selectionStart ?? 0;\n const { top, left, lineHeight } = getCaretCoordinates(el, caret);\n const field = el.getBoundingClientRect();\n this.caretOffset.dx = left - field.left;\n this.caretOffset.dy = top - field.top;\n this.caretOffset.lineHeight = lineHeight;\n\n if (!this.overlayRef) {\n this.overlayRef = this.overlay.create({\n // Flexible, not global: a global strategy has no anchor and no fallback\n // positions, so the panel was always drawn downward from the caret no\n // matter how little room was left below it — half the list ended up\n // under the fold, unreachable by pointer AND invisible to a sighted\n // keyboard user walking it with `aria-activedescendant` (WCAG 2.4.11).\n positionStrategy: this.overlay\n .position()\n .flexibleConnectedTo(this.caretAnchor())\n // The panel sizes itself (`max-height` on the list); the flip above\n // and `withPush` are the whole answer to a cramped viewport.\n .withFlexibleDimensions(false)\n .withPush(true)\n .withPositions(CARET_POSITIONS),\n scrollStrategy: this.scrollStrategies.reposition(),\n panelClass: 'wr-mention-overlay',\n });\n\n const portal = new ComponentPortal(WrMentionPanel);\n const ref = this.overlayRef.attach(portal);\n this.panelRef = ref;\n ref.setInput('displayWith', this.displayWith());\n ref.setInput('listboxId', this.listboxId);\n // Read once here rather than bound: the panel is created per open, and by\n // then the catalog has long landed.\n ref.setInput('listLabel', this.listLabel());\n ref.instance.picked.subscribe(item => this.commit(item as T));\n ref.instance.hovered.subscribe(i => this.setActive(i));\n }\n // No `else` re-building the strategy: the anchor reads `caretOffset` and the\n // field's live rect, both of which the lines above have already refreshed.\n\n this.panelRef?.setInput('items', this.filteredItems());\n this.panelRef?.setInput('activeIndex', this.activeIndex());\n\n // Render the panel's own view NOW, before `aria-activedescendant` is allowed\n // to name one of its options. `attach()` inserts the host element but leaves\n // it empty — the panel is a separate root view, so on the first open the\n // `<li>` being referenced does not exist anywhere in the document yet.\n // Waiting for the next tick would leave a window where the reference dangles\n // and the announcement is lost with no error anywhere.\n this.panelRef?.changeDetectorRef.detectChanges();\n // Place it only now, with the options rendered: which side of the caret the\n // panel takes is decided by measuring it, and `attach()` defers CDK's own\n // first placement into `afterNextRender` — a frame in which the panel would\n // be painted, full size and fully opaque, at the overlay container's\n // top-left corner.\n this.overlayRef.updatePosition();\n this.isOpen.set(true);\n }\n\n private close(): void {\n if (this.overlayRef) {\n this.overlayRef.dispose();\n this.overlayRef = null;\n this.panelRef = null;\n }\n this.isOpen.set(false);\n this.state = null;\n this.activeIndex.set(0);\n this.filteredItems.set([]);\n this.announce('');\n }\n\n private commit(item: T): void {\n // Snapshot the state up front. `dispatchEvent` below re-enters this directive\n // synchronously: the listener runs `detect()`, and because assigning\n // `el.value` has already parked the caret at the end of the text — past the\n // space this method appends — `detect()` finds no trigger and calls\n // `close()`, which nulls `this.state`. Reading `this.state` after that line\n // threw, so `wrMentionSelected` never fired for anyone.\n const state = this.state;\n if (!state) return;\n\n const el = this.host.nativeElement;\n const value = el.value;\n const caret = el.selectionStart ?? value.length;\n const valueFn = this.valueWith();\n const label = this.displayWith()(item);\n const inserted = valueFn ? valueFn(item, state.trigger) : `${state.trigger}${label}`;\n const replacement = `${inserted} `;\n const before = value.substring(0, state.startIndex);\n const after = value.substring(caret);\n const next = before + replacement + after;\n\n el.value = next;\n el.dispatchEvent(new Event('input', { bubbles: true }));\n const nextCaret = (before + replacement).length;\n el.setSelectionRange(nextCaret, nextCaret);\n this.wrMentionSelected.emit({ item, trigger: state.trigger, query: state.query });\n\n // Announce after `close()`, never before — it clears the live region.\n // Committing is otherwise silent: the panel simply vanishes.\n this.close();\n this.announce(this.insertedText({ label }));\n }\n}\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":"ngwr-mention.mjs","sources":["../../../projects/lib/mention/caret.ts","../../../projects/lib/mention/mention-panel.ts","../../../projects/lib/mention/mention-panel.html","../../../projects/lib/mention/mention.ts","../../../projects/lib/mention/ngwr-mention.ts"],"sourcesContent":["/**\n * @license\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://github.com/thekhegay/ngwr/blob/main/LICENSE\n */\n\n/**\n * CSS properties whose values must be copied onto the mirror to match layout.\n *\n * `as const satisfies` keeps each name checked against `CSSStyleDeclaration`\n * while narrowing the element type to these string-valued literals — the wider\n * `keyof CSSStyleDeclaration` also spans its methods (`setProperty`, `item`, …),\n * which makes the indexed read below look like an unbound method.\n */\nconst MIRROR_PROPS = [\n 'boxSizing',\n // Without this the mirror inherits `<body>`'s direction, and the two disagree\n // whenever the field carries its own `dir` — measured at 230px of error for a\n // 320px RTL field inside an LTR page, which put the panel outside the field\n // entirely. `textAlign` below is the same story from the other side: an RTL\n // field's computed value is the logical `start`, which only resolves to the\n // right edge once the mirror knows the direction.\n 'direction',\n 'width',\n 'height',\n 'overflowX',\n 'overflowY',\n 'borderTopWidth',\n 'borderRightWidth',\n 'borderBottomWidth',\n 'borderLeftWidth',\n 'paddingTop',\n 'paddingRight',\n 'paddingBottom',\n 'paddingLeft',\n 'fontStyle',\n 'fontVariant',\n 'fontWeight',\n 'fontStretch',\n 'fontSize',\n 'fontSizeAdjust',\n 'lineHeight',\n 'fontFamily',\n 'textAlign',\n 'textTransform',\n 'textIndent',\n 'textDecoration',\n 'letterSpacing',\n 'wordSpacing',\n 'tabSize',\n 'whiteSpace',\n 'wordWrap',\n] as const satisfies readonly (keyof CSSStyleDeclaration)[];\n\n/** A caret has no size, and an environment with no layout has no rects at all. */\nconst NO_RECT = { top: 0, left: 0 } as const;\n\n/**\n * Where the browser would draw the caret this collapsed `range` marks.\n *\n * `getClientRects()` before `getBoundingClientRect()`: a collapsed range at a\n * bidi boundary has TWO candidate positions and reports both, and the bounding\n * box of the pair spans the whole run between them. The first rect is the one\n * the caret is actually painted at.\n *\n * Both are optional-called because jsdom implements NEITHER on `Range` — it lays\n * nothing out, so every element rect in it is already 0×0 and a zero here keeps\n * the function answering what the rest of that environment answers rather than\n * throwing halfway through an unrelated spec.\n */\nfunction caretRectOf(range: Range): { readonly top: number; readonly left: number } {\n const rects = range.getClientRects?.();\n if (rects && rects.length > 0) return rects[0];\n return range.getBoundingClientRect?.() ?? NO_RECT;\n}\n\n/**\n * Compute the viewport coordinates of a textarea / input caret at a given\n * `position`. Uses the \"mirror div\" technique — a hidden div with the same\n * styling holds the field's text, and a COLLAPSED RANGE at `position` reports\n * where the caret would be.\n *\n * Returns viewport-relative `{ top, left }` — pass straight to a CDK\n * Overlay anchored at that point.\n *\n * **A range, not a probe span, and the difference only shows in RTL.** The\n * classic version of this technique puts the text before the caret in the div,\n * the text after it in a `<span>`, and reads the span's LEFT edge. That reads\n * the caret's side of the boundary only while the text runs left to right. Under\n * the Unicode BiDi algorithm the span can be laid out to the LEFT of the run it\n * follows, so the edge that faces the caret is its right one — and which edge\n * that is depends on the bidi level of the text at the caret, not on the field's\n * direction, so there is no side to pick. A collapsed range asks the browser\n * where it would draw the caret and gets the reordering for free. Measured\n * against a `contenteditable` twin of the field, in Chromium: the range agrees\n * to within a pixel for Latin, Arabic and mixed text in both directions, where\n * the span was out by up to 300px in RTL (see the spec next door).\n *\n * The `<input>` branch keeps the field's WIDTH for the same reason. It used to\n * force `width: auto` so a long single line could not wrap; but an RTL field\n * right-aligns its text inside its own box, so a shrink-to-fit mirror throws\n * away the only measurement that decides where the text starts. `overflow:\n * hidden` is what keeps the overflow from painting, and `input.scrollLeft`\n * already accounts for a field scrolled past its box.\n */\nexport function getCaretCoordinates(\n input: HTMLTextAreaElement | HTMLInputElement,\n position: number\n): { top: number; left: number; lineHeight: number } {\n const doc = input.ownerDocument;\n const style = getComputedStyle(input);\n const mirror = doc.createElement('div');\n\n for (const prop of MIRROR_PROPS) {\n mirror.style[prop] = style[prop];\n }\n mirror.style.position = 'absolute';\n mirror.style.visibility = 'hidden';\n mirror.style.top = '0';\n mirror.style.left = '0';\n mirror.style.whiteSpace = 'pre-wrap';\n mirror.style.wordWrap = 'break-word';\n\n // <input> is single-line — force whitespace behaviour so coords are sane.\n if (input.tagName === 'INPUT') {\n mirror.style.whiteSpace = 'pre';\n mirror.style.height = 'auto';\n mirror.style.overflow = 'hidden';\n }\n\n // One text node, so the range can address any offset in it. An empty field\n // still needs a line box to measure, hence the fallback character.\n const text = doc.createTextNode(input.value || '.');\n mirror.appendChild(text);\n\n doc.body.appendChild(mirror);\n const range = doc.createRange();\n range.setStart(text, Math.min(position, text.length));\n range.collapse(true);\n const caretRect = caretRectOf(range);\n const mirrorRect = mirror.getBoundingClientRect();\n const lineHeight = parseFloat(style.lineHeight) || parseFloat(style.fontSize) * 1.2;\n doc.body.removeChild(mirror);\n\n const inputRect = input.getBoundingClientRect();\n return {\n top: inputRect.top + (caretRect.top - mirrorRect.top) - input.scrollTop,\n left: inputRect.left + (caretRect.left - mirrorRect.left) - input.scrollLeft,\n lineHeight,\n };\n}\n","/**\n * @license\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://github.com/thekhegay/ngwr/blob/main/LICENSE\n */\n\nimport { Component, ViewEncapsulation, input, output } from '@angular/core';\n\nimport type { WrMentionItem } from './interfaces';\n\n/**\n * The id of the option at `index`, given the listbox's own id.\n *\n * Lives here, and is used from both sides, because this format is a contract\n * between two files: the panel stamps it onto each `<li>`, and the directive\n * names one of them in `aria-activedescendant`. Written out twice, the two would\n * drift and the reference would silently dangle — and a dangling\n * `aria-activedescendant` is not an error the browser reports, it just stops\n * announcing options.\n *\n * @internal\n */\nexport function wrMentionOptionId(listboxId: string, index: number): string {\n return `${listboxId}-opt-${index}`;\n}\n\n/**\n * Overlay panel rendered by {@link WrMention}. Not intended for\n * direct use — the directive instantiates it via a ComponentPortal.\n *\n * @internal — the same tag its twin `WrPopconfirmPanel` already carried. It says\n * the intent; it does not by itself keep the class out of the generated API\n * tables, because `WrTableFilter` and `WrTableSort` are tagged AND documented —\n * see `EXPORTED_BUT_INTERNAL` in `scripts/lib/extract-api.ts`.\n */\n@Component({\n selector: 'wr-mention-panel',\n templateUrl: './mention-panel.html',\n encapsulation: ViewEncapsulation.None,\n host: { class: 'wr-mention-panel' },\n})\nexport class WrMentionPanel {\n readonly items = input<readonly WrMentionItem[]>([]);\n readonly activeIndex = input(0);\n readonly displayWith = input<(item: WrMentionItem) => string>(item => item.label);\n\n /**\n * Id of the `<ul role=\"listbox\">`, minted by the directive and pointed at by\n * its `aria-controls`. Owned there rather than here because the panel is\n * disposed and rebuilt whenever the caret leaves a mention, while the\n * attribute on the host has to stay put.\n *\n * Plain `input('')`, never `input.required` — a ComponentPortal-created\n * component throws on the first change detection if a required input is unset.\n */\n readonly listboxId = input('');\n\n /** Accessible name for the listbox. ARIA requires one; the directive supplies it from the catalog. */\n readonly listLabel = input('');\n\n protected optionId(index: number): string {\n return wrMentionOptionId(this.listboxId(), index);\n }\n\n /** Emitted when the user picks an item (click). */\n readonly picked = output<WrMentionItem>();\n\n /** Emitted on mouseenter — used by the directive to track hover. */\n readonly hovered = output<number>();\n\n protected onPick(item: WrMentionItem, event: MouseEvent): void {\n // mousedown semantics — keep focus on the input.\n event.preventDefault();\n this.picked.emit(item);\n }\n}\n","<!--\n No empty branch: `activate()` calls `close()` and returns before `open()` when\n nothing matches, so the panel is never rendered with zero items. The \"No\n matches\" `<div>` that used to live here was unreachable — and a hard-coded\n English string besides. The zero-match case is announced through the\n directive's live region instead.\n-->\n<ul class=\"wr-mention-panel__list\" role=\"listbox\" [attr.id]=\"listboxId()\" [attr.aria-label]=\"listLabel()\">\n @for (item of items(); track $index) {\n <li\n class=\"wr-mention-panel__option\"\n role=\"option\"\n [attr.id]=\"optionId($index)\"\n [class.wr-mention-panel__option--active]=\"$index === activeIndex()\"\n [attr.aria-selected]=\"$index === activeIndex()\"\n (mouseenter)=\"hovered.emit($index)\"\n (mousedown)=\"onPick(item, $event)\"\n >\n {{ displayWith()(item) }}\n </li>\n }\n</ul>\n","/**\n * @license\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://github.com/thekhegay/ngwr/blob/main/LICENSE\n */\n\nimport { coerceNumberProperty } from '@angular/cdk/coercion';\nimport {\n type ConnectedPosition,\n type FlexibleConnectedPositionStrategyOrigin,\n type OverlayRef,\n ScrollStrategyOptions,\n} from '@angular/cdk/overlay';\nimport { ComponentPortal } from '@angular/cdk/portal';\nimport { isPlatformBrowser } from '@angular/common';\nimport {\n type ComponentRef,\n DestroyRef,\n Directive,\n ElementRef,\n PLATFORM_ID,\n computed,\n inject,\n input,\n output,\n signal,\n} from '@angular/core';\n\nimport { readI18nText, useI18nFormatter } from 'ngwr/i18n';\nimport { WR_OVERLAY } from 'ngwr/overlay';\nimport { isComposing } from 'ngwr/utils';\n\nimport { getCaretCoordinates } from './caret';\nimport type { WrMentionCommit, WrMentionItem } from './interfaces';\nimport { WrMentionPanel, wrMentionOptionId } from './mention-panel';\n\ninterface ActiveState {\n trigger: string;\n query: string;\n startIndex: number;\n}\n\n/**\n * Deterministic under prerender, unlike `randomId()` — `aria-controls` is a\n * static host binding, so it renders on the server too, and a random value would\n * differ between that render and the hydrated client.\n */\nlet mentionUid = 0;\n\n/** Gap between the caret's line and the panel, on whichever side it opens. */\nconst CARET_GAP = 4;\n\n/**\n * Below the caret's line first, above it when there is no room — the two sides\n * a caret-anchored list can take. There is no horizontal pair: a panel that\n * jumped to the other side of the caret would read as a different widget, so a\n * caret near the inline end is handled by `withPush` sliding the panel back\n * onto the screen instead. `overlayX: 'start'` is the logical corner, so RTL\n * hangs the panel from the caret's other side without a second table.\n */\nconst CARET_POSITIONS: ConnectedPosition[] = [\n { originX: 'start', originY: 'bottom', overlayX: 'start', overlayY: 'top' },\n { originX: 'start', originY: 'top', overlayX: 'start', overlayY: 'bottom' },\n];\n\n/**\n * `@`-style mention picker for `<textarea>` and `<input>`. Listens for\n * trigger characters (default `'@'`); when typed at a word boundary, an\n * overlay panel opens at the caret and shows filtered options. Pick with\n * Enter / Tab / click — the trigger + query is replaced with the inserted\n * value plus a trailing space.\n *\n * @example\n * ```html\n * <textarea\n * wrMention\n * [wrMentionItems]=\"users\"\n * [displayWith]=\"userLabel\"\n * (wrMentionSelected)=\"onMention($event)\"\n * ></textarea>\n * ```\n *\n * @see https://ngwr.dev/reference/components/mention\n */\n@Directive({\n selector: '[wrMention]',\n host: {\n '(input)': 'onInput()',\n '(click)': 'onClick()',\n '(keyup)': 'onCaretMove($event)',\n '(keydown)': 'onKeydown($event)',\n '(blur)': 'onBlur()',\n\n // The IME pair. `isComposing` on a `keydown` answers \"did the input method\n // take this key\", which is a different question from \"is a conversion open\n // right now\" — on the committing keystroke the two disagree by design — and\n // detection runs from `input` and `keyup`, where there is no such flag to\n // read. So the state is tracked here and the detector consults it.\n '(compositionstart)': 'onCompositionStart()',\n '(compositionend)': 'onCompositionEnd()',\n\n // The host keeps its implicit `role=\"textbox\"`. It is NOT a combobox: this\n // field holds prose and the mention is one fragment inside it, not the\n // field's value. `role=\"combobox\"` is also flatly disallowed on `<textarea>`\n // by ARIA in HTML (axe: `aria-allowed-role`), and it would drop\n // `aria-multiline`, which combobox does not support — so a screen reader\n // would stop reporting the field as multi-line for the whole editing\n // session, not just while the popup is open.\n //\n // Static, because `aria-autocomplete` describes a permanent capability of\n // the field; ARIA says authors SHOULD NOT toggle it to signal that\n // suggestions are currently showing. Being static is also what makes the\n // feature discoverable — it is announced on focus, before the user types a\n // trigger char.\n 'aria-autocomplete': 'list',\n // Required alongside `aria-autocomplete=\"list\"`, and must name the popup's\n // role. Measured clean on a textbox with axe-core 4.13.\n 'aria-haspopup': 'listbox',\n // Also static, and deliberately left pointing at an id that only exists\n // while the panel is open: gating it would mean gating `aria-autocomplete`\n // too. An unresolved `aria-controls` is a manual-review note in axe, never a\n // violation — unlike `aria-activedescendant`, which is an author error.\n //\n // This is the attribute that legalises the whole design: for a textbox\n // controlling a popup, `aria-activedescendant` is allowed to name an element\n // owned by the *controlled* element, which is how the reference reaches into\n // the CDK overlay's separate DOM subtree at all.\n '[attr.aria-controls]': 'listboxId',\n '[attr.aria-activedescendant]': 'activeOptionId()',\n\n // No `aria-expanded`. It is not a supported state of `role=\"textbox\"` —\n // axe rates it a *critical* `aria-allowed-attr` violation — so the\n // \"a list appeared\" signal cannot be an ARIA state here. That is what the\n // live region below is for; it is load-bearing, not decoration.\n },\n})\nexport class WrMention<T extends WrMentionItem = WrMentionItem> {\n /** Items to filter against the typed query. */\n readonly wrMentionItems = input<readonly T[]>([]);\n\n /** Trigger characters that open the panel. @default ['@'] */\n readonly triggers = input<readonly string[]>(['@']);\n\n /** Maps an item to its display label. @default `item.label` */\n readonly displayWith = input<(item: T) => string>((item: T) => item.label);\n\n /**\n * Returns the text to insert into the textarea on commit. Receives the\n * picked item and the trigger char. @default `${trigger}${displayWith(item)}`\n */\n readonly valueWith = input<((item: T, trigger: string) => string) | null>(null);\n\n /** Custom filter. @default case-insensitive `includes` over `displayWith(item)`. */\n readonly filterWith = input<((query: string, item: T) => boolean) | null>(null);\n\n /** Maximum number of items shown in the panel. @default 8 */\n readonly maxResults = input(8, { transform: (v: unknown): number => Math.max(1, coerceNumberProperty(v, 8)) });\n\n /** Emits the selected item, trigger, and query whenever the user commits. */\n readonly wrMentionSelected = output<WrMentionCommit<T>>();\n\n private readonly host = inject<ElementRef<HTMLTextAreaElement | HTMLInputElement>>(ElementRef);\n private readonly overlay = inject(WR_OVERLAY);\n private readonly scrollStrategies = inject(ScrollStrategyOptions);\n private readonly destroyRef = inject(DestroyRef);\n private readonly isBrowser = isPlatformBrowser(inject(PLATFORM_ID));\n\n private overlayRef: OverlayRef | null = null;\n private panelRef: ComponentRef<WrMentionPanel> | null = null;\n /**\n * Where the caret sits INSIDE the field — its offset from the field's\n * top-left corner, plus the height of the line it is on. Mutated in place by\n * `open()`, never replaced: `caretAnchor()` closes over this object.\n */\n private readonly caretOffset = { dx: 0, dy: 0, lineHeight: 0 };\n private state: ActiveState | null = null;\n private readonly activeIndex = signal(0);\n private readonly filteredItems = signal<readonly T[]>([]);\n private readonly isOpen = signal(false);\n\n /** @internal — read by the `aria-controls` host binding. */\n protected readonly listboxId = `wr-mention-listbox-${++mentionUid}`;\n\n private readonly listLabel = readI18nText('mention.listbox', 'Mentions');\n private readonly availableText = useI18nFormatter('mention.available', 'Matches available: {{count}}');\n private readonly insertedText = useI18nFormatter('mention.inserted', 'Inserted: {{label}}');\n\n /**\n * `aria-activedescendant`, or `null`.\n *\n * Gated hard on `isOpen()`, which is only set once the panel's options are\n * actually in the DOM. An id that resolves to nothing is an author error, and\n * the failure mode is silent: the browser does not complain, it simply stops\n * firing the event that makes a screen reader speak the option. Same\n * discipline `tree.ts` uses for its virtualized rows.\n */\n protected readonly activeOptionId = computed<string | null>(() => {\n if (!this.isOpen()) return null;\n const index = this.activeIndex();\n return index >= 0 && index < this.filteredItems().length ? wrMentionOptionId(this.listboxId, index) : null;\n });\n\n /**\n * Polite live region, parked on `<body>`.\n *\n * Not inside the panel: `close()` disposes the overlay on every keystroke that\n * leaves a mention, so a region living there would be recreated constantly —\n * and a live region has to be in the DOM *before* its text changes to be\n * announced at all. It also could not announce the two cases that matter most,\n * since in both the panel is already gone: \"nothing matched\" and \"inserted X\".\n *\n * Not a sibling of the host either — that would land inside the consumer's\n * markup and start matching their `+`, `~` and `:nth-child()` selectors.\n */\n private liveRegion: HTMLElement | null = null;\n\n constructor() {\n if (this.isBrowser) {\n const doc = this.host.nativeElement.ownerDocument;\n const region = doc.createElement('div');\n region.className = 'wr-mention__status';\n region.setAttribute('role', 'status');\n doc.body.appendChild(region);\n this.liveRegion = region;\n }\n\n this.destroyRef.onDestroy(() => {\n this.close();\n this.liveRegion?.remove();\n this.liveRegion = null;\n });\n }\n\n private announce(message: string): void {\n if (this.liveRegion) this.liveRegion.textContent = message;\n }\n\n // Host listeners\n\n /**\n * True between `compositionstart` and `compositionend`.\n *\n * Detection must not run on the half-built text an IME leaves in the field:\n * `@やま` is a reading on its way to `@山田`, and matching the reading opens a\n * panel that then eats the Enter meant to accept the conversion. So the field\n * is read once, on `compositionend`, when the text is real.\n */\n private composing = false;\n\n /** @internal */\n protected onCompositionStart(): void {\n this.composing = true;\n // A panel opened before the conversion started would take the arrows and the\n // Enter that now belong to the candidate window.\n this.close();\n }\n\n /** @internal */\n protected onCompositionEnd(): void {\n this.composing = false;\n // The committed text is only in the field now, so this is the first honest\n // read of it.\n this.detect();\n }\n\n /** @internal */\n protected onInput(): void {\n if (this.composing) return;\n this.detect();\n }\n\n /** @internal */\n protected onClick(): void {\n this.detect();\n }\n\n /** @internal */\n protected onCaretMove(event: KeyboardEvent): void {\n if (this.composing || isComposing(event)) return;\n // Every key `onKeydown` already consumed, Escape included: its keyup would\n // re-detect at a caret that is still sitting inside the mention and reopen the\n // panel Escape had just dismissed. A synthetic keydown-only Escape hides that\n // — the real gesture is a pair, and the second half undid the first.\n if (\n event.key === 'ArrowUp' ||\n event.key === 'ArrowDown' ||\n event.key === 'Enter' ||\n event.key === 'Tab' ||\n event.key === 'Escape'\n ) {\n return;\n }\n this.detect();\n }\n\n /** @internal */\n protected onKeydown(event: KeyboardEvent): void {\n // The key is the input method's while a conversion is open — Enter accepts a\n // candidate, Escape cancels the reading, the arrows walk the candidate list.\n // `this.composing` is deliberately not consulted: on Safari's committing\n // keystroke it has already gone false, and `isComposing` covers that case\n // through the 229 sentinel.\n if (isComposing(event)) return;\n if (!this.state) return;\n const list = this.filteredItems();\n switch (event.key) {\n case 'ArrowDown':\n event.preventDefault();\n if (list.length > 0) this.setActive((this.activeIndex() + 1) % list.length);\n break;\n case 'ArrowUp':\n event.preventDefault();\n if (list.length > 0) this.setActive((this.activeIndex() - 1 + list.length) % list.length);\n break;\n case 'Enter':\n case 'Tab': {\n const item = list[this.activeIndex()];\n if (item) {\n event.preventDefault();\n this.commit(item);\n }\n break;\n }\n case 'Escape':\n event.preventDefault();\n // The panel is the innermost thing Escape can dismiss, so it keeps the\n // key: without this, the same press also closed the dialog / drawer /\n // window the field sits in.\n //\n // `preventDefault()` alone buys nothing — every host overlay matches on\n // `event.key` and none of them looks at `defaultPrevented` — and letting\n // CDK's `OverlayKeyboardDispatcher` sort it out does not work either.\n // The dispatcher walks the stack newest-first and stops at the first\n // overlay with a `keydownEvents()` subscriber; this panel has none (it\n // is driven from the field's own handler, which runs at the target\n // before the body-phase dispatcher), so Escape falls straight through to\n // the overlay underneath.\n event.stopPropagation();\n this.close();\n break;\n default:\n break;\n }\n }\n\n /** @internal */\n protected onBlur(): void {\n // Defer so option mousedown lands first.\n setTimeout(() => this.close(), 120);\n }\n\n // Detection\n\n private detect(): void {\n const el = this.host.nativeElement;\n const caret = el.selectionStart ?? 0;\n const text = el.value.substring(0, caret);\n const triggers = this.triggers();\n\n // Walk back from the caret looking for a trigger char with whitespace\n // (or start-of-string) immediately before it. Stop on whitespace.\n let i = caret;\n while (i > 0) {\n const ch = text[i - 1];\n if (triggers.includes(ch)) {\n const before = i - 2 >= 0 ? text[i - 2] : '';\n if (i - 2 < 0 || /\\s/.test(before)) {\n const query = text.substring(i);\n this.activate(ch, query, i - 1);\n return;\n }\n break;\n }\n if (/\\s/.test(ch)) break;\n i--;\n }\n this.close();\n }\n\n private activate(trigger: string, query: string, startIndex: number): void {\n this.state = { trigger, query, startIndex };\n const previousCount = this.filteredItems().length;\n const filtered = this.filter(query).slice(0, this.maxResults());\n this.filteredItems.set(filtered);\n if (filtered.length === 0) {\n // Announce *after* closing: `close()` clears the region, so announcing\n // first would wipe the message before a screen reader reached it.\n this.close();\n this.announce(this.availableText({ count: 0 }));\n return;\n }\n this.setActive(Math.min(this.activeIndex(), filtered.length - 1));\n this.open();\n // Only when the size actually changes. Re-announcing the same count on every\n // keystroke would talk over the per-option speech from activedescendant.\n if (filtered.length !== previousCount) this.announce(this.availableText({ count: filtered.length }));\n }\n\n private filter(query: string): readonly T[] {\n const items = this.wrMentionItems();\n const custom = this.filterWith();\n const display = this.displayWith();\n if (!query) return items;\n if (custom) return items.filter(item => custom(query, item));\n const q = query.toLowerCase();\n return items.filter(item => display(item).toLowerCase().includes(q));\n }\n\n private setActive(i: number): void {\n this.activeIndex.set(i);\n this.panelRef?.setInput('activeIndex', i);\n }\n\n // Overlay\n\n /**\n * The overlay's anchor: the caret's line box, grown by the gap the panel\n * keeps from the text, as a zero-WIDTH virtual origin in viewport\n * coordinates. Inflating the rect rather than passing `offsetY` is what keeps\n * `withPush` honest — CDK applies a position offset as a `transform` AFTER it\n * has clamped, so a pushed panel would land the offset off the screen edge.\n *\n * Live getters, not a snapshot, and that is the whole of the scroll fix.\n * `getCaretCoordinates` answers in viewport coordinates, which a\n * `GlobalPositionStrategy` froze at the instant of the keystroke: the panel\n * then stayed pinned to the screen while the page scrolled out from under it\n * (150px of scroll, 150px of drift, ending up over unrelated content). CDK\n * re-reads a virtual origin on every `apply()`, so pinning it to the FIELD's\n * live rect is what turns `reposition()` into a real re-anchor.\n *\n * Only the caret's offset inside the field is captured up front. Measuring it\n * runs the mirror-div pass in `caret.ts` — a full layout read, far too\n * expensive to repeat per scroll event — and it cannot change without a\n * keystroke, which re-enters `open()` anyway.\n */\n private caretAnchor(): FlexibleConnectedPositionStrategyOrigin {\n const field = this.host.nativeElement;\n const offset = this.caretOffset;\n return {\n width: 0,\n get x(): number {\n return field.getBoundingClientRect().left + offset.dx;\n },\n get y(): number {\n return field.getBoundingClientRect().top + offset.dy - CARET_GAP;\n },\n get height(): number {\n return offset.lineHeight + CARET_GAP * 2;\n },\n };\n }\n\n private open(): void {\n const el = this.host.nativeElement;\n const caret = el.selectionStart ?? 0;\n const { top, left, lineHeight } = getCaretCoordinates(el, caret);\n const field = el.getBoundingClientRect();\n this.caretOffset.dx = left - field.left;\n this.caretOffset.dy = top - field.top;\n this.caretOffset.lineHeight = lineHeight;\n\n if (!this.overlayRef) {\n this.overlayRef = this.overlay.create({\n // Flexible, not global: a global strategy has no anchor and no fallback\n // positions, so the panel was always drawn downward from the caret no\n // matter how little room was left below it — half the list ended up\n // under the fold, unreachable by pointer AND invisible to a sighted\n // keyboard user walking it with `aria-activedescendant` (WCAG 2.4.11).\n positionStrategy: this.overlay\n .position()\n .flexibleConnectedTo(this.caretAnchor())\n // The panel sizes itself (`max-height` on the list); the flip above\n // and `withPush` are the whole answer to a cramped viewport.\n .withFlexibleDimensions(false)\n .withPush(true)\n .withPositions(CARET_POSITIONS),\n scrollStrategy: this.scrollStrategies.reposition(),\n panelClass: 'wr-mention-overlay',\n });\n\n const portal = new ComponentPortal(WrMentionPanel);\n const ref = this.overlayRef.attach(portal);\n this.panelRef = ref;\n ref.setInput('displayWith', this.displayWith());\n ref.setInput('listboxId', this.listboxId);\n // Read once here rather than bound: the panel is created per open, and by\n // then the catalog has long landed.\n ref.setInput('listLabel', this.listLabel());\n ref.instance.picked.subscribe(item => this.commit(item as T));\n ref.instance.hovered.subscribe(i => this.setActive(i));\n }\n // No `else` re-building the strategy: the anchor reads `caretOffset` and the\n // field's live rect, both of which the lines above have already refreshed.\n\n this.panelRef?.setInput('items', this.filteredItems());\n this.panelRef?.setInput('activeIndex', this.activeIndex());\n\n // Render the panel's own view NOW, before `aria-activedescendant` is allowed\n // to name one of its options. `attach()` inserts the host element but leaves\n // it empty — the panel is a separate root view, so on the first open the\n // `<li>` being referenced does not exist anywhere in the document yet.\n // Waiting for the next tick would leave a window where the reference dangles\n // and the announcement is lost with no error anywhere.\n this.panelRef?.changeDetectorRef.detectChanges();\n // Place it only now, with the options rendered: which side of the caret the\n // panel takes is decided by measuring it, and `attach()` defers CDK's own\n // first placement into `afterNextRender` — a frame in which the panel would\n // be painted, full size and fully opaque, at the overlay container's\n // top-left corner.\n this.overlayRef.updatePosition();\n this.isOpen.set(true);\n }\n\n private close(): void {\n if (this.overlayRef) {\n this.overlayRef.dispose();\n this.overlayRef = null;\n this.panelRef = null;\n }\n this.isOpen.set(false);\n this.state = null;\n this.activeIndex.set(0);\n this.filteredItems.set([]);\n this.announce('');\n }\n\n private commit(item: T): void {\n // Snapshot the state up front. `dispatchEvent` below re-enters this directive\n // synchronously: the listener runs `detect()`, and because assigning\n // `el.value` has already parked the caret at the end of the text — past the\n // space this method appends — `detect()` finds no trigger and calls\n // `close()`, which nulls `this.state`. Reading `this.state` after that line\n // threw, so `wrMentionSelected` never fired for anyone.\n const state = this.state;\n if (!state) return;\n\n const el = this.host.nativeElement;\n const value = el.value;\n const caret = el.selectionStart ?? value.length;\n const valueFn = this.valueWith();\n const label = this.displayWith()(item);\n const inserted = valueFn ? valueFn(item, state.trigger) : `${state.trigger}${label}`;\n const replacement = `${inserted} `;\n const before = value.substring(0, state.startIndex);\n const after = value.substring(caret);\n const next = before + replacement + after;\n\n el.value = next;\n el.dispatchEvent(new Event('input', { bubbles: true }));\n const nextCaret = (before + replacement).length;\n el.setSelectionRange(nextCaret, nextCaret);\n this.wrMentionSelected.emit({ item, trigger: state.trigger, query: state.query });\n\n // Announce after `close()`, never before — it clears the live region.\n // Committing is otherwise silent: the panel simply vanishes.\n this.close();\n this.announce(this.insertedText({ label }));\n }\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":[],"mappings":"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"}
|
|
@@ -11,14 +11,43 @@ import { WrVisualViewport } from 'ngwr/platform';
|
|
|
11
11
|
* Use of this source code is governed by an MIT-style license that can be
|
|
12
12
|
* found in the LICENSE file at https://github.com/thekhegay/ngwr/blob/main/LICENSE
|
|
13
13
|
*/
|
|
14
|
+
const SVG_NS = 'http://www.w3.org/2000/svg';
|
|
15
|
+
/** The same 24×24 stroked ✕ the rest of the library's chrome draws (alert, toast, lightbox). */
|
|
16
|
+
const CLOSE_ICON_ATTRS = [
|
|
17
|
+
['class', 'wr-icon__svg'],
|
|
18
|
+
['viewBox', '0 0 24 24'],
|
|
19
|
+
['fill', 'none'],
|
|
20
|
+
['stroke', 'currentColor'],
|
|
21
|
+
['stroke-width', '2'],
|
|
22
|
+
['stroke-linecap', 'round'],
|
|
23
|
+
['stroke-linejoin', 'round'],
|
|
24
|
+
['aria-hidden', 'true'],
|
|
25
|
+
];
|
|
26
|
+
const CLOSE_ICON_PATHS = ['M18 6 6 18', 'm6 6 12 12'];
|
|
14
27
|
/**
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
28
|
+
* Builds the ✕ node by node.
|
|
29
|
+
*
|
|
30
|
+
* It was one `innerHTML = CLOSE_ICON`, and the string was a compile-time
|
|
31
|
+
* constant, so it was never an injection risk — it was an AVAILABILITY one.
|
|
32
|
+
* Under `require-trusted-types-for 'script'` an `innerHTML` write throws
|
|
33
|
+
* `TrustedHTML assignment` whatever the string is, and the throw landed after
|
|
34
|
+
* the overlay was on screen: a modal with no ✕, no backdrop subscription and no
|
|
35
|
+
* Escape handler, over the whole application. `createElementNS` is not a
|
|
36
|
+
* Trusted Types sink, so there is nothing left to police.
|
|
18
37
|
*/
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
38
|
+
function createCloseIcon(doc) {
|
|
39
|
+
const svg = doc.createElementNS(SVG_NS, 'svg');
|
|
40
|
+
// `setAttribute`, not `.className` — on an SVG element that property is an
|
|
41
|
+
// `SVGAnimatedString` and assigning a string to it silently does nothing.
|
|
42
|
+
for (const [name, value] of CLOSE_ICON_ATTRS)
|
|
43
|
+
svg.setAttribute(name, value);
|
|
44
|
+
for (const d of CLOSE_ICON_PATHS) {
|
|
45
|
+
const path = doc.createElementNS(SVG_NS, 'path');
|
|
46
|
+
path.setAttribute('d', d);
|
|
47
|
+
svg.appendChild(path);
|
|
48
|
+
}
|
|
49
|
+
return svg;
|
|
50
|
+
}
|
|
22
51
|
/**
|
|
23
52
|
* Appends a dismiss (✕) button to a service-opened overlay panel.
|
|
24
53
|
*
|
|
@@ -41,7 +70,7 @@ function wrAppendOverlayClose(host, className, label, onClose) {
|
|
|
41
70
|
button.type = 'button';
|
|
42
71
|
button.className = className;
|
|
43
72
|
button.setAttribute('aria-label', label);
|
|
44
|
-
button.
|
|
73
|
+
button.appendChild(createCloseIcon(host.ownerDocument));
|
|
45
74
|
button.addEventListener('click', onClose);
|
|
46
75
|
host.appendChild(button);
|
|
47
76
|
return button;
|