@ng-doc/app 22.0.0 → 22.2.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/LICENSE +21 -0
- package/fesm2022/ng-doc-app-classes-content-controller.mjs +22 -4
- package/fesm2022/ng-doc-app-classes-content-controller.mjs.map +1 -1
- package/fesm2022/ng-doc-app-classes-default-search-engine.mjs +36 -12
- package/fesm2022/ng-doc-app-classes-default-search-engine.mjs.map +1 -1
- package/fesm2022/ng-doc-app-classes-root-page.mjs.map +1 -1
- package/fesm2022/ng-doc-app-components-api-list.mjs +7 -5
- package/fesm2022/ng-doc-app-components-api-list.mjs.map +1 -1
- package/fesm2022/ng-doc-app-components-breadcrumb.mjs +5 -3
- package/fesm2022/ng-doc-app-components-breadcrumb.mjs.map +1 -1
- package/fesm2022/ng-doc-app-components-code.mjs +6 -4
- package/fesm2022/ng-doc-app-components-code.mjs.map +1 -1
- package/fesm2022/ng-doc-app-components-command-palette.mjs +35 -27
- package/fesm2022/ng-doc-app-components-command-palette.mjs.map +1 -1
- package/fesm2022/ng-doc-app-components-copy-button.mjs +6 -4
- package/fesm2022/ng-doc-app-components-copy-button.mjs.map +1 -1
- package/fesm2022/ng-doc-app-components-demo-displayer.mjs +19 -4
- package/fesm2022/ng-doc-app-components-demo-displayer.mjs.map +1 -1
- package/fesm2022/ng-doc-app-components-demo-frame.mjs +279 -0
- package/fesm2022/ng-doc-app-components-demo-frame.mjs.map +1 -0
- package/fesm2022/ng-doc-app-components-demo-pane.mjs +2 -2
- package/fesm2022/ng-doc-app-components-demo-pane.mjs.map +1 -1
- package/fesm2022/ng-doc-app-components-demo.mjs +97 -14
- package/fesm2022/ng-doc-app-components-demo.mjs.map +1 -1
- package/fesm2022/ng-doc-app-components-fullscreen-button.mjs +5 -3
- package/fesm2022/ng-doc-app-components-fullscreen-button.mjs.map +1 -1
- package/fesm2022/ng-doc-app-components-fullscreen-toggle.mjs +4 -2
- package/fesm2022/ng-doc-app-components-fullscreen-toggle.mjs.map +1 -1
- package/fesm2022/ng-doc-app-components-heading-anchor.mjs +5 -3
- package/fesm2022/ng-doc-app-components-heading-anchor.mjs.map +1 -1
- package/fesm2022/ng-doc-app-components-image-viewer.mjs +5 -3
- package/fesm2022/ng-doc-app-components-image-viewer.mjs.map +1 -1
- package/fesm2022/ng-doc-app-components-navbar.mjs +4 -2
- package/fesm2022/ng-doc-app-components-navbar.mjs.map +1 -1
- package/fesm2022/ng-doc-app-components-page-navigation.mjs +5 -3
- package/fesm2022/ng-doc-app-components-page-navigation.mjs.map +1 -1
- package/fesm2022/ng-doc-app-components-page-wrapper.mjs +2 -2
- package/fesm2022/ng-doc-app-components-page-wrapper.mjs.map +1 -1
- package/fesm2022/ng-doc-app-components-page.mjs +83 -16
- package/fesm2022/ng-doc-app-components-page.mjs.map +1 -1
- package/fesm2022/ng-doc-app-components-playground.mjs +838 -141
- package/fesm2022/ng-doc-app-components-playground.mjs.map +1 -1
- package/fesm2022/ng-doc-app-components-root.mjs +51 -4
- package/fesm2022/ng-doc-app-components-root.mjs.map +1 -1
- package/fesm2022/ng-doc-app-components-search-result.mjs +8 -6
- package/fesm2022/ng-doc-app-components-search-result.mjs.map +1 -1
- package/fesm2022/ng-doc-app-components-search.mjs +7 -5
- package/fesm2022/ng-doc-app-components-search.mjs.map +1 -1
- package/fesm2022/ng-doc-app-components-sidebar.mjs +4 -3
- package/fesm2022/ng-doc-app-components-sidebar.mjs.map +1 -1
- package/fesm2022/ng-doc-app-components-tabs.mjs +21 -3
- package/fesm2022/ng-doc-app-components-tabs.mjs.map +1 -1
- package/fesm2022/ng-doc-app-components-theme-toggle.mjs +7 -5
- package/fesm2022/ng-doc-app-components-theme-toggle.mjs.map +1 -1
- package/fesm2022/ng-doc-app-components-toc.mjs +4 -2
- package/fesm2022/ng-doc-app-components-toc.mjs.map +1 -1
- package/fesm2022/ng-doc-app-components.mjs +1 -0
- package/fesm2022/ng-doc-app-components.mjs.map +1 -1
- package/fesm2022/ng-doc-app-constants.mjs +11 -1
- package/fesm2022/ng-doc-app-constants.mjs.map +1 -1
- package/fesm2022/ng-doc-app-demo-app.mjs +779 -0
- package/fesm2022/ng-doc-app-demo-app.mjs.map +1 -0
- package/fesm2022/ng-doc-app-helpers.mjs +147 -7
- package/fesm2022/ng-doc-app-helpers.mjs.map +1 -1
- package/fesm2022/ng-doc-app-processors-processors.mjs +30 -9
- package/fesm2022/ng-doc-app-processors-processors.mjs.map +1 -1
- package/fesm2022/ng-doc-app-providers-ng-doc-app.mjs +29 -18
- package/fesm2022/ng-doc-app-providers-ng-doc-app.mjs.map +1 -1
- package/fesm2022/ng-doc-app-providers-texts.mjs +53 -0
- package/fesm2022/ng-doc-app-providers-texts.mjs.map +1 -0
- package/fesm2022/ng-doc-app-providers-title.mjs +129 -0
- package/fesm2022/ng-doc-app-providers-title.mjs.map +1 -0
- package/fesm2022/ng-doc-app-providers.mjs +2 -0
- package/fesm2022/ng-doc-app-providers.mjs.map +1 -1
- package/fesm2022/ng-doc-app-services-content-scroll-intent.mjs +27 -2
- package/fesm2022/ng-doc-app-services-content-scroll-intent.mjs.map +1 -1
- package/fesm2022/ng-doc-app-services-highlighter.mjs +23 -16
- package/fesm2022/ng-doc-app-services-highlighter.mjs.map +1 -1
- package/fesm2022/ng-doc-app-services-route-preloader.mjs +34 -10
- package/fesm2022/ng-doc-app-services-route-preloader.mjs.map +1 -1
- package/fesm2022/ng-doc-app-services-shortcuts.mjs +38 -4
- package/fesm2022/ng-doc-app-services-shortcuts.mjs.map +1 -1
- package/fesm2022/ng-doc-app-texts-ar.mjs +238 -0
- package/fesm2022/ng-doc-app-texts-ar.mjs.map +1 -0
- package/fesm2022/ng-doc-app-texts-de.mjs +205 -0
- package/fesm2022/ng-doc-app-texts-de.mjs.map +1 -0
- package/fesm2022/ng-doc-app-texts-es.mjs +205 -0
- package/fesm2022/ng-doc-app-texts-es.mjs.map +1 -0
- package/fesm2022/ng-doc-app-texts-fr.mjs +205 -0
- package/fesm2022/ng-doc-app-texts-fr.mjs.map +1 -0
- package/fesm2022/ng-doc-app-texts-hi.mjs +219 -0
- package/fesm2022/ng-doc-app-texts-hi.mjs.map +1 -0
- package/fesm2022/ng-doc-app-texts-ja.mjs +205 -0
- package/fesm2022/ng-doc-app-texts-ja.mjs.map +1 -0
- package/fesm2022/ng-doc-app-texts-ko.mjs +205 -0
- package/fesm2022/ng-doc-app-texts-ko.mjs.map +1 -0
- package/fesm2022/ng-doc-app-texts-pt-br.mjs +205 -0
- package/fesm2022/ng-doc-app-texts-pt-br.mjs.map +1 -0
- package/fesm2022/ng-doc-app-texts-ru.mjs +231 -0
- package/fesm2022/ng-doc-app-texts-ru.mjs.map +1 -0
- package/fesm2022/ng-doc-app-texts-uk.mjs +231 -0
- package/fesm2022/ng-doc-app-texts-uk.mjs.map +1 -0
- package/fesm2022/ng-doc-app-texts-zh.mjs +205 -0
- package/fesm2022/ng-doc-app-texts-zh.mjs.map +1 -0
- package/fesm2022/ng-doc-app-tokens.mjs +214 -1
- package/fesm2022/ng-doc-app-tokens.mjs.map +1 -1
- package/fesm2022/ng-doc-app-type-controls-json-control.mjs +90 -0
- package/fesm2022/ng-doc-app-type-controls-json-control.mjs.map +1 -0
- package/fesm2022/ng-doc-app-type-controls-list-control.mjs +792 -0
- package/fesm2022/ng-doc-app-type-controls-list-control.mjs.map +1 -0
- package/fesm2022/ng-doc-app-type-controls-type-alias-control.mjs +45 -8
- package/fesm2022/ng-doc-app-type-controls-type-alias-control.mjs.map +1 -1
- package/fesm2022/ng-doc-app-type-controls.mjs +2 -0
- package/fesm2022/ng-doc-app-type-controls.mjs.map +1 -1
- package/package.json +73 -4
- package/styles/global.css +4 -0
- package/styles/main.css +4 -0
- package/styles/themes/dark.css +10 -0
- package/types/ng-doc-app-classes-default-search-engine.d.ts +11 -0
- package/types/ng-doc-app-classes-root-page.d.ts +13 -0
- package/types/ng-doc-app-components-api-list.d.ts +3 -1
- package/types/ng-doc-app-components-breadcrumb.d.ts +2 -1
- package/types/ng-doc-app-components-code.d.ts +2 -0
- package/types/ng-doc-app-components-command-palette.d.ts +3 -1
- package/types/ng-doc-app-components-copy-button.d.ts +3 -1
- package/types/ng-doc-app-components-demo-displayer.d.ts +13 -1
- package/types/ng-doc-app-components-demo-frame.d.ts +107 -0
- package/types/ng-doc-app-components-demo.d.ts +47 -10
- package/types/ng-doc-app-components-fullscreen-button.d.ts +2 -0
- package/types/ng-doc-app-components-fullscreen-toggle.d.ts +2 -0
- package/types/ng-doc-app-components-heading-anchor.d.ts +2 -0
- package/types/ng-doc-app-components-image-viewer.d.ts +2 -0
- package/types/ng-doc-app-components-navbar.d.ts +2 -0
- package/types/ng-doc-app-components-page-navigation.d.ts +2 -1
- package/types/ng-doc-app-components-page.d.ts +10 -1
- package/types/ng-doc-app-components-playground.d.ts +260 -42
- package/types/ng-doc-app-components-root.d.ts +18 -0
- package/types/ng-doc-app-components-search-result.d.ts +2 -0
- package/types/ng-doc-app-components-search.d.ts +2 -0
- package/types/ng-doc-app-components-sidebar.d.ts +2 -1
- package/types/ng-doc-app-components-tabs.d.ts +13 -3
- package/types/ng-doc-app-components-theme-toggle.d.ts +4 -1
- package/types/ng-doc-app-components-toc.d.ts +2 -1
- package/types/ng-doc-app-components.d.ts +1 -0
- package/types/ng-doc-app-constants.d.ts +11 -1
- package/types/ng-doc-app-demo-app.d.ts +297 -0
- package/types/ng-doc-app-helpers.d.ts +75 -4
- package/types/ng-doc-app-interfaces.d.ts +635 -6
- package/types/ng-doc-app-processors-processors.d.ts +5 -0
- package/types/ng-doc-app-providers-ng-doc-app.d.ts +7 -1
- package/types/ng-doc-app-providers-texts.d.ts +28 -0
- package/types/ng-doc-app-providers-title.d.ts +59 -0
- package/types/ng-doc-app-providers.d.ts +2 -0
- package/types/ng-doc-app-services-content-scroll-intent.d.ts +11 -1
- package/types/ng-doc-app-services-highlighter.d.ts +15 -6
- package/types/ng-doc-app-services-route-preloader.d.ts +13 -1
- package/types/ng-doc-app-services-shortcuts.d.ts +16 -0
- package/types/ng-doc-app-texts-ar.d.ts +13 -0
- package/types/ng-doc-app-texts-de.d.ts +12 -0
- package/types/ng-doc-app-texts-es.d.ts +12 -0
- package/types/ng-doc-app-texts-fr.d.ts +12 -0
- package/types/ng-doc-app-texts-hi.d.ts +12 -0
- package/types/ng-doc-app-texts-ja.d.ts +12 -0
- package/types/ng-doc-app-texts-ko.d.ts +12 -0
- package/types/ng-doc-app-texts-pt-br.d.ts +12 -0
- package/types/ng-doc-app-texts-ru.d.ts +12 -0
- package/types/ng-doc-app-texts-uk.d.ts +12 -0
- package/types/ng-doc-app-texts-zh.d.ts +12 -0
- package/types/ng-doc-app-tokens.d.ts +21 -2
- package/types/ng-doc-app-type-controls-json-control.d.ts +38 -0
- package/types/ng-doc-app-type-controls-list-control.d.ts +313 -0
- package/types/ng-doc-app-type-controls-type-alias-control.d.ts +28 -5
- package/types/ng-doc-app-type-controls.d.ts +2 -0
|
@@ -0,0 +1,792 @@
|
|
|
1
|
+
import * as i0 from '@angular/core';
|
|
2
|
+
import { input, inject, computed, signal, Injector, viewChild, effect, untracked, afterNextRender, Directive, ChangeDetectionStrategy, Component } from '@angular/core';
|
|
3
|
+
import { resolvePlaygroundOption } from '@ng-doc/app/helpers';
|
|
4
|
+
import { NG_DOC_TEXTS } from '@ng-doc/app/tokens';
|
|
5
|
+
import { NgDocDropdownComponent, NgDocButtonIconComponent, NgDocDropdownOriginDirective, NgDocIconComponent, NgDocTagComponent } from '@ng-doc/ui-kit';
|
|
6
|
+
import { DIControl } from 'di-controls';
|
|
7
|
+
|
|
8
|
+
/** How far, in pixels, a pointer moves on a chip before it drags the chip. */
|
|
9
|
+
const DRAG_THRESHOLD = 4;
|
|
10
|
+
let nextId = 0;
|
|
11
|
+
/**
|
|
12
|
+
* The base of the playground controls of array inputs: a chip combobox. The items are chips, in
|
|
13
|
+
* one field with a text field after them. Enter adds the typed value, Backspace in the empty field
|
|
14
|
+
* removes the last chip, and the × of a chip removes it. A chip is dragged to another place, or
|
|
15
|
+
* moved with Alt+Left and Alt+Right while it is focused, and the arrow keys move between chips.
|
|
16
|
+
* While the reader types, a dropdown lists the matching suggestions; the arrow keys move its
|
|
17
|
+
* highlight, and Enter or a click picks a row. Escape closes it.
|
|
18
|
+
*
|
|
19
|
+
* Subclasses decide what an item is: typed text (`parseItem`) for the arrays of strings and
|
|
20
|
+
* numbers, which suggest the input's default items and the items removed while the control is
|
|
21
|
+
* shown, or the values of a union, which the dropdown lists (`NgDocTypeAliasListControlComponent`).
|
|
22
|
+
*
|
|
23
|
+
* The control never changes an array it received: every edit reports a new array of plain values,
|
|
24
|
+
* so the value can be compared with the default and sent to an isolated demo as it is. An
|
|
25
|
+
* `undefined` or `null` value has no items and shows as such; the first added item makes it an
|
|
26
|
+
* array, and removing the last item leaves an empty array. Reset returns to the input's default,
|
|
27
|
+
* which is how an optional input gets back to `undefined`.
|
|
28
|
+
*/
|
|
29
|
+
class NgDocListControl extends DIControl {
|
|
30
|
+
constructor() {
|
|
31
|
+
super({
|
|
32
|
+
// The form writes back each value the control reports. Another value, such as the
|
|
33
|
+
// inspector's Reset, ends an edit or a drag of the items that it replaces.
|
|
34
|
+
onIncomingUpdate: (value) => {
|
|
35
|
+
if (value !== this.reported) {
|
|
36
|
+
this.editing.set(null);
|
|
37
|
+
this.cancelDrag();
|
|
38
|
+
}
|
|
39
|
+
},
|
|
40
|
+
});
|
|
41
|
+
/** Default value of the input; Reset returns to it. */
|
|
42
|
+
this.default = input(undefined, /* @ts-ignore */
|
|
43
|
+
...(ngDevMode ? [{ debugName: "default" }] : /* istanbul ignore next */ []));
|
|
44
|
+
this.texts = inject(NG_DOC_TEXTS).playground;
|
|
45
|
+
/** The prefix of the IDs of the parts of the control. */
|
|
46
|
+
this.id = `ng-doc-list-control-${nextId++}`;
|
|
47
|
+
/** The items of the value; none when the value is not an array. */
|
|
48
|
+
this.items = computed(() => {
|
|
49
|
+
const value = this.model();
|
|
50
|
+
return Array.isArray(value) ? value : [];
|
|
51
|
+
}, /* @ts-ignore */
|
|
52
|
+
...(ngDevMode ? [{ debugName: "items" }] : /* istanbul ignore next */ []));
|
|
53
|
+
/** The items as the chips show them: in the order of a drag in progress. */
|
|
54
|
+
this.shownItems = computed(() => this.dragOrder() ?? this.items(), /* @ts-ignore */
|
|
55
|
+
...(ngDevMode ? [{ debugName: "shownItems" }] : /* istanbul ignore next */ []));
|
|
56
|
+
/**
|
|
57
|
+
* The text shown instead of the items when the value is not an array (`undefined` or `null`), or
|
|
58
|
+
* is an empty array (`[]`), so that the two can be told apart. These are code, not words.
|
|
59
|
+
*/
|
|
60
|
+
this.literal = computed(() => {
|
|
61
|
+
const value = this.model();
|
|
62
|
+
if (Array.isArray(value)) {
|
|
63
|
+
return value.length ? '' : '[]';
|
|
64
|
+
}
|
|
65
|
+
return value === null ? 'null' : 'undefined';
|
|
66
|
+
}, /* @ts-ignore */
|
|
67
|
+
...(ngDevMode ? [{ debugName: "literal" }] : /* istanbul ignore next */ []));
|
|
68
|
+
/** Whether Reset is shown: the value differs from the default. */
|
|
69
|
+
this.canReset = computed(() => !sameItems(this.model(), this.default()), /* @ts-ignore */
|
|
70
|
+
...(ngDevMode ? [{ debugName: "canReset" }] : /* istanbul ignore next */ []));
|
|
71
|
+
/** The text of the text field. */
|
|
72
|
+
this.text = signal('', /* @ts-ignore */
|
|
73
|
+
...(ngDevMode ? [{ debugName: "text" }] : /* istanbul ignore next */ []));
|
|
74
|
+
/** Whether the reader asked for the dropdown, by typing, clicking or an arrow key. */
|
|
75
|
+
this.expanded = signal(false, /* @ts-ignore */
|
|
76
|
+
...(ngDevMode ? [{ debugName: "expanded" }] : /* istanbul ignore next */ []));
|
|
77
|
+
/** The position of the highlighted row of the dropdown, or `-1`. */
|
|
78
|
+
this.highlighted = signal(-1, /* @ts-ignore */
|
|
79
|
+
...(ngDevMode ? [{ debugName: "highlighted" }] : /* istanbul ignore next */ []));
|
|
80
|
+
/** The position of the item edited in place, or `null`. */
|
|
81
|
+
this.editing = signal(null, /* @ts-ignore */
|
|
82
|
+
...(ngDevMode ? [{ debugName: "editing" }] : /* istanbul ignore next */ []));
|
|
83
|
+
/** The text of the field that edits an item in place. */
|
|
84
|
+
this.editText = signal('', /* @ts-ignore */
|
|
85
|
+
...(ngDevMode ? [{ debugName: "editText" }] : /* istanbul ignore next */ []));
|
|
86
|
+
/** The position of the chip in the tab order; the others are reached with the arrow keys. */
|
|
87
|
+
this.tabStop = signal(0, /* @ts-ignore */
|
|
88
|
+
...(ngDevMode ? [{ debugName: "tabStop" }] : /* istanbul ignore next */ []));
|
|
89
|
+
/** The position of the chip being dragged, or `null`. */
|
|
90
|
+
this.dragIndex = signal(null, /* @ts-ignore */
|
|
91
|
+
...(ngDevMode ? [{ debugName: "dragIndex" }] : /* istanbul ignore next */ []));
|
|
92
|
+
/** What the live region announces: the new position of a moved item. */
|
|
93
|
+
this.announcement = signal('', /* @ts-ignore */
|
|
94
|
+
...(ngDevMode ? [{ debugName: "announcement" }] : /* istanbul ignore next */ []));
|
|
95
|
+
/** The width of the dropdown: the width of the field. */
|
|
96
|
+
this.panelWidth = signal(0, /* @ts-ignore */
|
|
97
|
+
...(ngDevMode ? [{ debugName: "panelWidth" }] : /* istanbul ignore next */ []));
|
|
98
|
+
/** Whether the text field holds text that is not an item, such as a word in a `number[]`. */
|
|
99
|
+
this.invalid = computed(() => this.freeText && this.text().trim() !== '' && this.parseItem(this.text()) === undefined, /* @ts-ignore */
|
|
100
|
+
...(ngDevMode ? [{ debugName: "invalid" }] : /* istanbul ignore next */ []));
|
|
101
|
+
/** Whether the field that edits an item holds text that is not an item. */
|
|
102
|
+
this.editInvalid = computed(() => this.editing() !== null && this.parseItem(this.editText()) === undefined, /* @ts-ignore */
|
|
103
|
+
...(ngDevMode ? [{ debugName: "editInvalid" }] : /* istanbul ignore next */ []));
|
|
104
|
+
/**
|
|
105
|
+
* The rows of the dropdown: the values of `candidates()` whose text contains the typed text (the
|
|
106
|
+
* one equal to it first), and, when the typed text is an item that no row adds, a row that adds
|
|
107
|
+
* it, first.
|
|
108
|
+
*/
|
|
109
|
+
this.rows = computed(() => {
|
|
110
|
+
const text = this.text();
|
|
111
|
+
const query = text.trim().toLowerCase();
|
|
112
|
+
const items = this.items();
|
|
113
|
+
const typed = this.freeText && query ? this.parseItem(text) : undefined;
|
|
114
|
+
const rows = this.candidates()
|
|
115
|
+
.map((value) => ({
|
|
116
|
+
kind: 'item',
|
|
117
|
+
value,
|
|
118
|
+
label: this.labelOf(value),
|
|
119
|
+
selected: items.some((item) => Object.is(item, value)),
|
|
120
|
+
}))
|
|
121
|
+
.filter((row) => row.label.toLowerCase().includes(query));
|
|
122
|
+
const exact = typed === undefined
|
|
123
|
+
? -1
|
|
124
|
+
: rows.findIndex((row) => Object.is(row.value, typed));
|
|
125
|
+
if (exact > 0) {
|
|
126
|
+
rows.unshift(...rows.splice(exact, 1));
|
|
127
|
+
}
|
|
128
|
+
return typed !== undefined && exact < 0
|
|
129
|
+
? [{ kind: 'add', value: typed, label: this.labelOf(typed), selected: false }, ...rows]
|
|
130
|
+
: rows;
|
|
131
|
+
}, /* @ts-ignore */
|
|
132
|
+
...(ngDevMode ? [{ debugName: "rows" }] : /* istanbul ignore next */ []));
|
|
133
|
+
/**
|
|
134
|
+
* Whether the dropdown is open: the reader asked for it and it has rows, or, for a union, says
|
|
135
|
+
* that no value matches the typed text.
|
|
136
|
+
*/
|
|
137
|
+
this.panelOpen = computed(() => this.expanded() &&
|
|
138
|
+
!this.disabled &&
|
|
139
|
+
(this.rows().length > 0 || (!this.freeText && this.text().trim() !== '')), /* @ts-ignore */
|
|
140
|
+
...(ngDevMode ? [{ debugName: "panelOpen" }] : /* istanbul ignore next */ []));
|
|
141
|
+
/** The items removed while the control is shown, which the arrays of strings and numbers suggest. */
|
|
142
|
+
this.removed = signal([], /* @ts-ignore */
|
|
143
|
+
...(ngDevMode ? [{ debugName: "removed" }] : /* istanbul ignore next */ []));
|
|
144
|
+
this.injector = inject(Injector);
|
|
145
|
+
this.dropdown = viewChild(NgDocDropdownComponent, /* @ts-ignore */
|
|
146
|
+
...(ngDevMode ? [{ debugName: "dropdown" }] : /* istanbul ignore next */ []));
|
|
147
|
+
this.fieldBox = viewChild('fieldBox', /* @ts-ignore */
|
|
148
|
+
...(ngDevMode ? [{ debugName: "fieldBox" }] : /* istanbul ignore next */ []));
|
|
149
|
+
/** The items in the order of a drag in progress, or `null`. */
|
|
150
|
+
this.dragOrder = signal(null, /* @ts-ignore */
|
|
151
|
+
...(ngDevMode ? [{ debugName: "dragOrder" }] : /* istanbul ignore next */ []));
|
|
152
|
+
this.drag = null;
|
|
153
|
+
/** The last array the control reported, which the form writes back. */
|
|
154
|
+
this.reported = null;
|
|
155
|
+
// The dropdown is an overlay; it follows the state, and it closes itself on an outside click.
|
|
156
|
+
effect(() => {
|
|
157
|
+
const open = this.panelOpen();
|
|
158
|
+
const dropdown = this.dropdown();
|
|
159
|
+
untracked(() => {
|
|
160
|
+
if (open) {
|
|
161
|
+
this.panelWidth.set(this.fieldBox()?.nativeElement.offsetWidth ?? 0);
|
|
162
|
+
dropdown?.open();
|
|
163
|
+
}
|
|
164
|
+
else {
|
|
165
|
+
dropdown?.close();
|
|
166
|
+
}
|
|
167
|
+
});
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* The values that the dropdown can list, before the typed text filters them: for an array of
|
|
172
|
+
* strings or numbers, the items of the default and the removed items that the array doesn't
|
|
173
|
+
* hold. Plain arrays may hold an item twice, so typed text that is an item is added again, but
|
|
174
|
+
* the suggestions never repeat an item.
|
|
175
|
+
*/
|
|
176
|
+
candidates() {
|
|
177
|
+
const items = this.items();
|
|
178
|
+
const pool = [...(this.default() ?? []), ...this.removed()];
|
|
179
|
+
return pool.filter((value, index) => pool.findIndex((other) => Object.is(other, value)) === index &&
|
|
180
|
+
!items.some((item) => Object.is(item, value)));
|
|
181
|
+
}
|
|
182
|
+
/**
|
|
183
|
+
* The text of an item.
|
|
184
|
+
* @param item - The item.
|
|
185
|
+
*/
|
|
186
|
+
labelOf(item) {
|
|
187
|
+
return String(item);
|
|
188
|
+
}
|
|
189
|
+
/**
|
|
190
|
+
* The text of a chip: the item's text, or `""` for an empty string.
|
|
191
|
+
* @param item - The item.
|
|
192
|
+
*/
|
|
193
|
+
chipText(item) {
|
|
194
|
+
return this.labelOf(item) || '""';
|
|
195
|
+
}
|
|
196
|
+
/**
|
|
197
|
+
* Reads the text field as the reader types.
|
|
198
|
+
* @param text - The text of the field.
|
|
199
|
+
*/
|
|
200
|
+
type(text) {
|
|
201
|
+
this.text.set(text);
|
|
202
|
+
this.expanded.set(true);
|
|
203
|
+
// With text, the first row is the typed value or the value that matches it best.
|
|
204
|
+
this.highlighted.set(text.trim() && this.rows().length ? 0 : -1);
|
|
205
|
+
}
|
|
206
|
+
/**
|
|
207
|
+
* Handles the keys of the text field.
|
|
208
|
+
* @param event - The key event.
|
|
209
|
+
* @param field - The text field.
|
|
210
|
+
*/
|
|
211
|
+
fieldKeydown(event, field) {
|
|
212
|
+
const count = this.items().length;
|
|
213
|
+
switch (event.key) {
|
|
214
|
+
case 'Enter':
|
|
215
|
+
event.preventDefault();
|
|
216
|
+
this.enter();
|
|
217
|
+
break;
|
|
218
|
+
case 'ArrowDown':
|
|
219
|
+
case 'ArrowUp':
|
|
220
|
+
event.preventDefault();
|
|
221
|
+
this.moveHighlight(event.key === 'ArrowDown' ? 1 : -1);
|
|
222
|
+
break;
|
|
223
|
+
case 'Escape':
|
|
224
|
+
if (this.panelOpen()) {
|
|
225
|
+
// The dropdown closes; the key does not reach the page.
|
|
226
|
+
event.preventDefault();
|
|
227
|
+
event.stopPropagation();
|
|
228
|
+
this.expanded.set(false);
|
|
229
|
+
}
|
|
230
|
+
break;
|
|
231
|
+
case 'Backspace':
|
|
232
|
+
if (field.value === '' && count) {
|
|
233
|
+
event.preventDefault();
|
|
234
|
+
this.remove(count - 1);
|
|
235
|
+
}
|
|
236
|
+
break;
|
|
237
|
+
case 'ArrowLeft':
|
|
238
|
+
case 'ArrowRight':
|
|
239
|
+
// The key toward the chips, at the start of the field, focuses the last chip.
|
|
240
|
+
if (event.key === (this.isRtl() ? 'ArrowRight' : 'ArrowLeft') &&
|
|
241
|
+
field.selectionStart === 0 &&
|
|
242
|
+
field.selectionEnd === 0 &&
|
|
243
|
+
count) {
|
|
244
|
+
event.preventDefault();
|
|
245
|
+
this.expanded.set(false);
|
|
246
|
+
this.focusChip(count - 1);
|
|
247
|
+
}
|
|
248
|
+
break;
|
|
249
|
+
case 'Tab':
|
|
250
|
+
this.expanded.set(false);
|
|
251
|
+
break;
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
/**
|
|
255
|
+
* Opens the dropdown when the reader clicks the field, and focuses the text field when the click
|
|
256
|
+
* is not on a chip or a button.
|
|
257
|
+
* @param event - The click.
|
|
258
|
+
* @param field - The text field.
|
|
259
|
+
*/
|
|
260
|
+
fieldClick(event, field) {
|
|
261
|
+
const target = event.target instanceof Element ? event.target : null;
|
|
262
|
+
if (this.disabled || target?.closest('[data-ng-doc-list-item], button')) {
|
|
263
|
+
return;
|
|
264
|
+
}
|
|
265
|
+
field.focus();
|
|
266
|
+
this.expanded.set(true);
|
|
267
|
+
}
|
|
268
|
+
/**
|
|
269
|
+
* Picks a row of the dropdown: adds its value, or removes a picked value of a union.
|
|
270
|
+
* @param row - The row.
|
|
271
|
+
*/
|
|
272
|
+
pick(row) {
|
|
273
|
+
if (!this.freeText && row.selected) {
|
|
274
|
+
this.remove(this.items().findIndex((item) => Object.is(item, row.value)), false);
|
|
275
|
+
}
|
|
276
|
+
else {
|
|
277
|
+
this.add(row.value);
|
|
278
|
+
}
|
|
279
|
+
this.text.set('');
|
|
280
|
+
if (this.freeText) {
|
|
281
|
+
this.expanded.set(false);
|
|
282
|
+
this.highlighted.set(-1);
|
|
283
|
+
}
|
|
284
|
+
else {
|
|
285
|
+
// A union keeps its values open, so that the reader can pick several; the highlight stays on
|
|
286
|
+
// the picked value.
|
|
287
|
+
this.highlighted.set(this.rows().findIndex((other) => Object.is(other.value, row.value)));
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
/**
|
|
291
|
+
* Handles the keys of a focused chip.
|
|
292
|
+
* @param event - The key event.
|
|
293
|
+
* @param index - The position of the chip.
|
|
294
|
+
*/
|
|
295
|
+
chipKeydown(event, index) {
|
|
296
|
+
const count = this.items().length;
|
|
297
|
+
const forward = this.isRtl() ? 'ArrowLeft' : 'ArrowRight';
|
|
298
|
+
switch (event.key) {
|
|
299
|
+
case 'ArrowLeft':
|
|
300
|
+
case 'ArrowRight': {
|
|
301
|
+
const offset = event.key === forward ? 1 : -1;
|
|
302
|
+
event.preventDefault();
|
|
303
|
+
if (event.altKey) {
|
|
304
|
+
this.move(index, offset);
|
|
305
|
+
}
|
|
306
|
+
else if (index + offset >= count) {
|
|
307
|
+
this.focusField();
|
|
308
|
+
}
|
|
309
|
+
else {
|
|
310
|
+
this.focusChip(Math.max(index + offset, 0));
|
|
311
|
+
}
|
|
312
|
+
break;
|
|
313
|
+
}
|
|
314
|
+
case 'Home':
|
|
315
|
+
case 'End':
|
|
316
|
+
event.preventDefault();
|
|
317
|
+
this.focusChip(event.key === 'Home' ? 0 : count - 1);
|
|
318
|
+
break;
|
|
319
|
+
case 'Delete':
|
|
320
|
+
case 'Backspace':
|
|
321
|
+
event.preventDefault();
|
|
322
|
+
this.remove(index);
|
|
323
|
+
break;
|
|
324
|
+
case 'Enter':
|
|
325
|
+
case 'F2':
|
|
326
|
+
if (this.freeText) {
|
|
327
|
+
event.preventDefault();
|
|
328
|
+
this.startEdit(index);
|
|
329
|
+
}
|
|
330
|
+
break;
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
/**
|
|
334
|
+
* Removes an item. The focus goes to the chip that takes its place, the previous chip, or the
|
|
335
|
+
* text field when the field had it, or no chip is left.
|
|
336
|
+
* @param index - The position of the item.
|
|
337
|
+
* @param focus - Whether the focus moves; a pick in the dropdown keeps it in the text field.
|
|
338
|
+
*/
|
|
339
|
+
remove(index, focus = true) {
|
|
340
|
+
const items = this.items();
|
|
341
|
+
const value = items[index];
|
|
342
|
+
const rest = items.filter((_item, position) => position !== index);
|
|
343
|
+
const fromField = this.isFocused(this.fieldElement());
|
|
344
|
+
if (!this.removed().some((item) => Object.is(item, value))) {
|
|
345
|
+
this.removed.set([...this.removed(), value]);
|
|
346
|
+
}
|
|
347
|
+
this.report(rest);
|
|
348
|
+
if (!focus) {
|
|
349
|
+
return;
|
|
350
|
+
}
|
|
351
|
+
if (fromField || !rest.length) {
|
|
352
|
+
this.focusField();
|
|
353
|
+
}
|
|
354
|
+
else {
|
|
355
|
+
this.focusChip(Math.min(index, rest.length - 1));
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
/**
|
|
359
|
+
* Edits an item in place: its chip becomes a text field.
|
|
360
|
+
* @param index - The position of the item.
|
|
361
|
+
*/
|
|
362
|
+
startEdit(index) {
|
|
363
|
+
if (!this.freeText || this.disabled) {
|
|
364
|
+
return;
|
|
365
|
+
}
|
|
366
|
+
this.expanded.set(false);
|
|
367
|
+
this.editText.set(String(this.items()[index]));
|
|
368
|
+
this.editing.set(index);
|
|
369
|
+
afterNextRender(() => {
|
|
370
|
+
const field = this.host().querySelector('[data-ng-doc-list-edit]');
|
|
371
|
+
field?.focus();
|
|
372
|
+
field?.select();
|
|
373
|
+
}, { injector: this.injector });
|
|
374
|
+
}
|
|
375
|
+
/**
|
|
376
|
+
* Handles the keys of the field that edits an item: Enter keeps the text, Escape the item.
|
|
377
|
+
* @param event - The key event.
|
|
378
|
+
*/
|
|
379
|
+
editKeydown(event) {
|
|
380
|
+
const index = this.editing();
|
|
381
|
+
if (index === null) {
|
|
382
|
+
return;
|
|
383
|
+
}
|
|
384
|
+
if (event.key === 'Enter') {
|
|
385
|
+
event.preventDefault();
|
|
386
|
+
if (this.commitEdit()) {
|
|
387
|
+
this.focusChip(index);
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
else if (event.key === 'Escape') {
|
|
391
|
+
event.preventDefault();
|
|
392
|
+
event.stopPropagation();
|
|
393
|
+
this.editing.set(null);
|
|
394
|
+
this.focusChip(index);
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
/** Ends an edit when its field loses the focus: with the text when it is an item. */
|
|
398
|
+
editBlur() {
|
|
399
|
+
if (this.editing() !== null && !this.commitEdit()) {
|
|
400
|
+
this.editing.set(null);
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
/**
|
|
404
|
+
* Starts to follow a pointer that went down on a chip.
|
|
405
|
+
* @param event - The pointer event.
|
|
406
|
+
* @param index - The position of the chip.
|
|
407
|
+
*/
|
|
408
|
+
chipPointerDown(event, index) {
|
|
409
|
+
if (this.disabled || event.button !== 0 || this.editing() !== null) {
|
|
410
|
+
return;
|
|
411
|
+
}
|
|
412
|
+
this.drag = {
|
|
413
|
+
pointerId: event.pointerId,
|
|
414
|
+
x: event.clientX,
|
|
415
|
+
y: event.clientY,
|
|
416
|
+
index,
|
|
417
|
+
started: false,
|
|
418
|
+
};
|
|
419
|
+
// The chip keeps the pointer while it moves over the other chips.
|
|
420
|
+
event.currentTarget?.setPointerCapture?.(event.pointerId);
|
|
421
|
+
}
|
|
422
|
+
/**
|
|
423
|
+
* Drags the chip once the pointer has moved far enough, and moves it in front of or behind the
|
|
424
|
+
* chip under the pointer once the pointer passes that chip's middle.
|
|
425
|
+
* @param event - The pointer event.
|
|
426
|
+
*/
|
|
427
|
+
chipPointerMove(event) {
|
|
428
|
+
const drag = this.drag;
|
|
429
|
+
if (!drag || drag.pointerId !== event.pointerId) {
|
|
430
|
+
return;
|
|
431
|
+
}
|
|
432
|
+
if (!drag.started) {
|
|
433
|
+
if (Math.hypot(event.clientX - drag.x, event.clientY - drag.y) < DRAG_THRESHOLD) {
|
|
434
|
+
return;
|
|
435
|
+
}
|
|
436
|
+
drag.started = true;
|
|
437
|
+
this.expanded.set(false);
|
|
438
|
+
this.dragOrder.set([...this.items()]);
|
|
439
|
+
this.dragIndex.set(drag.index);
|
|
440
|
+
}
|
|
441
|
+
const from = this.dragIndex();
|
|
442
|
+
const order = this.dragOrder();
|
|
443
|
+
const target = from === null ? null : this.dropTarget(event, from);
|
|
444
|
+
if (from !== null && order && target !== null) {
|
|
445
|
+
this.dragOrder.set(moveItem(order, from, target));
|
|
446
|
+
this.dragIndex.set(target);
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
/**
|
|
450
|
+
* Drops a dragged chip: the items take the order of the drag.
|
|
451
|
+
* @param event - The pointer event.
|
|
452
|
+
*/
|
|
453
|
+
chipPointerUp(event) {
|
|
454
|
+
const drag = this.drag;
|
|
455
|
+
if (!drag || drag.pointerId !== event.pointerId) {
|
|
456
|
+
return;
|
|
457
|
+
}
|
|
458
|
+
const order = this.dragOrder();
|
|
459
|
+
const index = this.dragIndex();
|
|
460
|
+
this.cancelDrag();
|
|
461
|
+
if (drag.started && order && index !== null && index !== drag.index) {
|
|
462
|
+
this.report(order);
|
|
463
|
+
this.announceMove(order, index);
|
|
464
|
+
this.focusChip(index);
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
/** Stops following the pointer; a drag in progress leaves the items as they were. */
|
|
468
|
+
cancelDrag() {
|
|
469
|
+
this.drag = null;
|
|
470
|
+
this.dragOrder.set(null);
|
|
471
|
+
this.dragIndex.set(null);
|
|
472
|
+
}
|
|
473
|
+
/**
|
|
474
|
+
* Closes the dropdown and marks the control as touched when the focus leaves the control.
|
|
475
|
+
* @param event - The focus event.
|
|
476
|
+
*/
|
|
477
|
+
leave(event) {
|
|
478
|
+
const next = event.relatedTarget instanceof Node ? event.relatedTarget : null;
|
|
479
|
+
if (!next || !this.host().contains(next)) {
|
|
480
|
+
this.expanded.set(false);
|
|
481
|
+
this.touch();
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
/** Sets the value back to the default of the input. */
|
|
485
|
+
reset() {
|
|
486
|
+
const value = this.default();
|
|
487
|
+
this.text.set('');
|
|
488
|
+
this.editing.set(null);
|
|
489
|
+
this.report(Array.isArray(value) ? [...value] : value);
|
|
490
|
+
}
|
|
491
|
+
/**
|
|
492
|
+
* The ID of a row of the dropdown, which the text field names as its active descendant.
|
|
493
|
+
* @param index - The position of the row.
|
|
494
|
+
*/
|
|
495
|
+
rowId(index) {
|
|
496
|
+
return `${this.id}-row-${index}`;
|
|
497
|
+
}
|
|
498
|
+
/**
|
|
499
|
+
* Adds an item at the end.
|
|
500
|
+
* @param value - The item.
|
|
501
|
+
*/
|
|
502
|
+
add(value) {
|
|
503
|
+
this.report([...this.items(), value]);
|
|
504
|
+
}
|
|
505
|
+
/** Adds the highlighted row, or the typed text when it is an item. */
|
|
506
|
+
enter() {
|
|
507
|
+
const rows = this.rows();
|
|
508
|
+
const row = this.panelOpen()
|
|
509
|
+
? rows[this.highlighted()]
|
|
510
|
+
: undefined;
|
|
511
|
+
if (row) {
|
|
512
|
+
this.pick(row);
|
|
513
|
+
return;
|
|
514
|
+
}
|
|
515
|
+
const typed = this.freeText && this.text().trim() ? this.parseItem(this.text()) : undefined;
|
|
516
|
+
if (typed !== undefined) {
|
|
517
|
+
this.add(typed);
|
|
518
|
+
this.text.set('');
|
|
519
|
+
this.expanded.set(false);
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
/**
|
|
523
|
+
* Moves the highlight of the dropdown, and opens it.
|
|
524
|
+
* @param step - `1` for the next row, `-1` for the previous one.
|
|
525
|
+
*/
|
|
526
|
+
moveHighlight(step) {
|
|
527
|
+
const count = this.rows().length;
|
|
528
|
+
const current = this.panelOpen() ? this.highlighted() : -1;
|
|
529
|
+
this.expanded.set(true);
|
|
530
|
+
if (count) {
|
|
531
|
+
this.highlighted.set(current < 0 ? (step > 0 ? 0 : count - 1) : (current + step + count) % count);
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
/**
|
|
535
|
+
* Moves an item one place, with the focus.
|
|
536
|
+
* @param index - The position of the item.
|
|
537
|
+
* @param offset - `-1` toward the start, `1` toward the end.
|
|
538
|
+
*/
|
|
539
|
+
move(index, offset) {
|
|
540
|
+
const target = index + offset;
|
|
541
|
+
const items = this.items();
|
|
542
|
+
if (target < 0 || target >= items.length) {
|
|
543
|
+
return;
|
|
544
|
+
}
|
|
545
|
+
const order = moveItem(items, index, target);
|
|
546
|
+
this.report(order);
|
|
547
|
+
this.announceMove(order, target);
|
|
548
|
+
this.focusChip(target);
|
|
549
|
+
}
|
|
550
|
+
/**
|
|
551
|
+
* Sets the edited item from the text of its field.
|
|
552
|
+
* @returns Whether the text is an item.
|
|
553
|
+
*/
|
|
554
|
+
commitEdit() {
|
|
555
|
+
const index = this.editing();
|
|
556
|
+
const value = this.parseItem(this.editText());
|
|
557
|
+
if (index === null || value === undefined) {
|
|
558
|
+
return false;
|
|
559
|
+
}
|
|
560
|
+
this.editing.set(null);
|
|
561
|
+
if (!Object.is(value, this.items()[index])) {
|
|
562
|
+
this.report(this.items().map((item, position) => (position === index ? value : item)));
|
|
563
|
+
}
|
|
564
|
+
return true;
|
|
565
|
+
}
|
|
566
|
+
/**
|
|
567
|
+
* The position a dragged chip moves to: the position of the chip under the pointer, once the
|
|
568
|
+
* pointer has passed its middle in the direction of the move.
|
|
569
|
+
* @param event - The pointer event.
|
|
570
|
+
* @param from - The position of the dragged chip.
|
|
571
|
+
*/
|
|
572
|
+
dropTarget(event, from) {
|
|
573
|
+
const rtl = this.isRtl();
|
|
574
|
+
for (const chip of Array.from(this.host().querySelectorAll('[data-ng-doc-list-item]'))) {
|
|
575
|
+
const index = Number(chip.getAttribute('data-ng-doc-list-item'));
|
|
576
|
+
const rect = chip.getBoundingClientRect();
|
|
577
|
+
if (index === from ||
|
|
578
|
+
event.clientX < rect.left ||
|
|
579
|
+
event.clientX > rect.right ||
|
|
580
|
+
event.clientY < rect.top ||
|
|
581
|
+
event.clientY > rect.bottom) {
|
|
582
|
+
continue;
|
|
583
|
+
}
|
|
584
|
+
const middle = rect.left + rect.width / 2;
|
|
585
|
+
const pastMiddle = index > from !== rtl ? event.clientX > middle : event.clientX < middle;
|
|
586
|
+
return pastMiddle ? index : null;
|
|
587
|
+
}
|
|
588
|
+
return null;
|
|
589
|
+
}
|
|
590
|
+
/**
|
|
591
|
+
* Reports a new value.
|
|
592
|
+
* @param value - The value.
|
|
593
|
+
*/
|
|
594
|
+
report(value) {
|
|
595
|
+
this.reported = value;
|
|
596
|
+
this.updateModel(value);
|
|
597
|
+
}
|
|
598
|
+
/**
|
|
599
|
+
* Announces the new position of a moved item.
|
|
600
|
+
* @param order - The items after the move.
|
|
601
|
+
* @param index - The new position of the item.
|
|
602
|
+
*/
|
|
603
|
+
announceMove(order, index) {
|
|
604
|
+
this.announcement.set(this.texts.itemMoved({
|
|
605
|
+
item: this.chipText(order[index]),
|
|
606
|
+
position: index + 1,
|
|
607
|
+
count: order.length,
|
|
608
|
+
}));
|
|
609
|
+
}
|
|
610
|
+
/**
|
|
611
|
+
* Focuses a chip once the chips have rendered: they are tracked by position, so the focused
|
|
612
|
+
* element may show another item, or be gone.
|
|
613
|
+
* @param index - The position of the chip.
|
|
614
|
+
*/
|
|
615
|
+
focusChip(index) {
|
|
616
|
+
this.tabStop.set(index);
|
|
617
|
+
afterNextRender(() => this.host()
|
|
618
|
+
.querySelector(`[data-ng-doc-list-item="${index}"] [data-ng-doc-list-chip]`)
|
|
619
|
+
?.focus(), { injector: this.injector });
|
|
620
|
+
}
|
|
621
|
+
/** Focuses the text field once the chips have rendered. */
|
|
622
|
+
focusField() {
|
|
623
|
+
afterNextRender(() => this.fieldElement()?.focus(), { injector: this.injector });
|
|
624
|
+
}
|
|
625
|
+
fieldElement() {
|
|
626
|
+
return this.host().querySelector('[data-ng-doc-list-field]');
|
|
627
|
+
}
|
|
628
|
+
isFocused(element) {
|
|
629
|
+
return !!element && element.ownerDocument.activeElement === element;
|
|
630
|
+
}
|
|
631
|
+
/** Whether the chips flow from right to left, which turns the arrow keys round. */
|
|
632
|
+
isRtl() {
|
|
633
|
+
const host = this.host();
|
|
634
|
+
return host.ownerDocument.defaultView?.getComputedStyle(host).direction === 'rtl';
|
|
635
|
+
}
|
|
636
|
+
host() {
|
|
637
|
+
return this.elementRef.nativeElement;
|
|
638
|
+
}
|
|
639
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.2.1", ngImport: i0, type: NgDocListControl, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
|
|
640
|
+
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.2.0", version: "22.2.1", type: NgDocListControl, isStandalone: true, inputs: { default: { classPropertyName: "default", publicName: "default", isSignal: true, isRequired: false, transformFunction: null } }, host: { listeners: { "focusout": "leave($event)" } }, viewQueries: [{ propertyName: "dropdown", first: true, predicate: NgDocDropdownComponent, descendants: true, isSignal: true }, { propertyName: "fieldBox", first: true, predicate: ["fieldBox"], descendants: true, isSignal: true }], usesInheritance: true, ngImport: i0 }); }
|
|
641
|
+
}
|
|
642
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.2.1", ngImport: i0, type: NgDocListControl, decorators: [{
|
|
643
|
+
type: Directive,
|
|
644
|
+
args: [{
|
|
645
|
+
host: {
|
|
646
|
+
'(focusout)': 'leave($event)',
|
|
647
|
+
},
|
|
648
|
+
}]
|
|
649
|
+
}], ctorParameters: () => [], propDecorators: { default: [{ type: i0.Input, args: [{ isSignal: true, alias: "default", required: false }] }], dropdown: [{ type: i0.ViewChild, args: [i0.forwardRef(() => NgDocDropdownComponent), { isSignal: true }] }], fieldBox: [{ type: i0.ViewChild, args: ['fieldBox', { isSignal: true }] }] } });
|
|
650
|
+
/** The shared metadata of the array controls. */
|
|
651
|
+
const LIST_CONTROL_IMPORTS = [
|
|
652
|
+
NgDocButtonIconComponent,
|
|
653
|
+
NgDocDropdownComponent,
|
|
654
|
+
NgDocDropdownOriginDirective,
|
|
655
|
+
NgDocIconComponent,
|
|
656
|
+
NgDocTagComponent,
|
|
657
|
+
];
|
|
658
|
+
/**
|
|
659
|
+
* The playground control of `string[]` inputs: a chip combobox (`NgDocListControl`). Typed text is
|
|
660
|
+
* an item as it is, and a double click, Enter or F2 edits a chip in place.
|
|
661
|
+
*/
|
|
662
|
+
class NgDocStringListControlComponent extends NgDocListControl {
|
|
663
|
+
constructor() {
|
|
664
|
+
super();
|
|
665
|
+
this.freeText = true;
|
|
666
|
+
}
|
|
667
|
+
parseItem(text) {
|
|
668
|
+
return text;
|
|
669
|
+
}
|
|
670
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.2.1", ngImport: i0, type: NgDocStringListControlComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
671
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.2.1", type: NgDocStringListControlComponent, isStandalone: true, selector: "ng-doc-string-list-control", usesInheritance: true, ngImport: i0, template: "<!-- The chips and the text field share one field-styled box, which the dropdown is attached to. -->\n<div\n #fieldBox\n class=\"ng-doc-list-control-box\"\n ngDocDropdownOrigin\n [attr.data-ng-doc-invalid]=\"invalid() || editInvalid()\"\n [attr.data-ng-doc-disabled]=\"disabled\"\n (click)=\"fieldClick($event, field)\">\n <div class=\"ng-doc-list-control-flow\">\n <!-- `display: contents` lets the chips and the text field wrap as one line of content. -->\n <ul class=\"ng-doc-list-control-chips\" role=\"list\">\n @for (item of shownItems(); track $index) {\n <li\n class=\"ng-doc-list-control-item\"\n [attr.data-ng-doc-list-item]=\"$index\"\n [attr.data-ng-doc-dragging]=\"dragIndex() === $index\">\n @if (editing() === $index) {\n <input\n class=\"ng-doc-list-control-edit\"\n data-ng-doc-list-edit\n spellcheck=\"false\"\n autocomplete=\"off\"\n [size]=\"editText().length || 1\"\n [value]=\"editText()\"\n [attr.aria-label]=\"texts.editItem({ item: chipText(item) })\"\n [attr.aria-invalid]=\"editInvalid() ? 'true' : null\"\n [attr.aria-describedby]=\"editInvalid() ? id + '-error' : null\"\n (input)=\"editText.set(edit.value)\"\n (keydown)=\"editKeydown($event)\"\n (blur)=\"editBlur()\"\n #edit />\n } @else {\n <ng-doc-tag class=\"ng-doc-list-control-chip\" mod=\"light\">\n <span\n class=\"ng-doc-list-control-chip-text\"\n data-ng-doc-list-chip\n [class.ng-doc-list-control-empty-string]=\"labelOf(item) === ''\"\n [attr.role]=\"freeText ? 'button' : null\"\n [attr.tabindex]=\"\n disabled ? null : $index === tabStop() || ($last && tabStop() > $index) ? 0 : -1\n \"\n [attr.aria-describedby]=\"\n freeText ? id + '-edit-keys ' + id + '-keys' : id + '-keys'\n \"\n (focus)=\"tabStop.set($index)\"\n (keydown)=\"chipKeydown($event, $index)\"\n (dblclick)=\"startEdit($index)\"\n (pointerdown)=\"chipPointerDown($event, $index)\"\n (pointermove)=\"chipPointerMove($event)\"\n (pointerup)=\"chipPointerUp($event)\"\n (pointercancel)=\"cancelDrag()\"\n >{{ chipText(item) }}</span\n >\n <button\n type=\"button\"\n class=\"ng-doc-list-control-remove\"\n data-ng-doc-list-remove\n tabindex=\"-1\"\n [attr.aria-label]=\"texts.removeItem({ item: chipText(item) })\"\n [disabled]=\"disabled\"\n (click)=\"remove($index)\">\n <ng-doc-icon icon=\"x\" aria-hidden=\"true\"></ng-doc-icon>\n </button>\n </ng-doc-tag>\n }\n </li>\n }\n </ul>\n @if (literal(); as literal) {\n <code class=\"ng-doc-list-control-literal\" data-ng-doc-list-literal>{{ literal }}</code>\n }\n <input\n #field\n class=\"ng-doc-list-control-field\"\n data-ng-doc-list-field\n role=\"combobox\"\n aria-autocomplete=\"list\"\n spellcheck=\"false\"\n autocomplete=\"off\"\n [value]=\"text()\"\n [placeholder]=\"items().length ? '' : texts.addItem\"\n [disabled]=\"disabled\"\n [attr.aria-label]=\"texts.addItem\"\n [attr.aria-expanded]=\"panelOpen()\"\n [attr.aria-controls]=\"id + '-rows'\"\n [attr.aria-activedescendant]=\"panelOpen() && highlighted() >= 0 ? rowId(highlighted()) : null\"\n [attr.aria-invalid]=\"invalid() ? 'true' : null\"\n [attr.aria-describedby]=\"invalid() ? id + '-error' : null\"\n (input)=\"type(field.value)\"\n (keydown)=\"fieldKeydown($event, field)\" />\n </div>\n @if (canReset()) {\n <button\n ng-doc-button-icon-transparent\n type=\"button\"\n size=\"small\"\n class=\"ng-doc-list-control-reset\"\n data-ng-doc-list-reset\n [rounded]=\"false\"\n [attr.aria-label]=\"texts.resetToDefault\"\n [disabled]=\"disabled\"\n (click)=\"reset()\">\n <ng-doc-icon icon=\"x\" aria-hidden=\"true\"></ng-doc-icon>\n </button>\n }\n <!-- The dropdown's host takes the focus while it is open, to move it into the overlay. The rows\n here are never focused (the text field names the highlighted one), so the host is inert. -->\n <span class=\"ng-doc-list-control-dropdown\" inert>\n <ng-doc-dropdown\n [content]=\"panel\"\n [positions]=\"['bottom-center', 'top-center']\"\n [maxHeight]=\"240\"\n (beforeClose)=\"expanded.set(false)\"></ng-doc-dropdown>\n </span>\n</div>\n@if (invalid() || editInvalid()) {\n <span class=\"ng-doc-list-control-error\" [id]=\"id + '-error'\">{{ texts.invalidNumber }}</span>\n}\n<span hidden [id]=\"id + '-keys'\">{{ texts.itemKeys }}</span>\n<span hidden [id]=\"id + '-edit-keys'\">{{ texts.editKeys }}</span>\n<span class=\"ng-doc-list-control-status\" role=\"status\">{{ announcement() }}</span>\n\n<ng-template #panel>\n <!-- A press on a row keeps the focus in the text field. -->\n <div\n class=\"ng-doc-list-control-panel\"\n [style.width.px]=\"panelWidth()\"\n (mousedown)=\"$event.preventDefault()\">\n @if (rows().length) {\n <div\n role=\"listbox\"\n class=\"ng-doc-list-control-rows\"\n [id]=\"id + '-rows'\"\n [attr.aria-label]=\"texts.suggestions\"\n [attr.aria-multiselectable]=\"freeText ? null : 'true'\">\n @for (row of rows(); track $index) {\n <div\n role=\"option\"\n class=\"ng-doc-list-control-row\"\n [id]=\"rowId($index)\"\n [attr.data-ng-doc-list-row]=\"row.kind\"\n [attr.data-ng-doc-highlighted]=\"highlighted() === $index\"\n [attr.aria-selected]=\"freeText ? highlighted() === $index : row.selected\"\n (pointerenter)=\"highlighted.set($index)\"\n (click)=\"pick(row)\">\n <span class=\"ng-doc-list-control-row-text\">\n {{ row.kind === 'add' ? texts.addValue({ value: row.label }) : row.label || '\"\"' }}\n </span>\n @if (row.selected) {\n <ng-doc-icon\n class=\"ng-doc-list-control-check\"\n icon=\"check\"\n aria-hidden=\"true\"></ng-doc-icon>\n }\n </div>\n }\n </div>\n } @else {\n <div class=\"ng-doc-list-control-no-matches\" role=\"status\">{{ texts.noMatches }}</div>\n }\n </div>\n</ng-template>\n", styles: [":host{display:flex;flex-direction:column;gap:var(--ng-doc-space-1);width:100%;min-width:0}.ng-doc-list-control-box{display:flex;align-items:flex-start;box-sizing:border-box;width:100%;min-width:0;min-height:var(--ng-doc-input-height);padding:5px var(--ng-doc-space-1) 5px 5px;border:var(--ng-doc-input-border);border-radius:var(--ng-doc-floated-border-radius);background-color:var(--ng-doc-input-background-color);cursor:text}.ng-doc-list-control-box:hover:not([data-ng-doc-disabled=true]){border:var(--ng-doc-input-border-hover)}.ng-doc-list-control-box:has(:focus-visible){box-shadow:var(--ng-doc-focus-ring)}.ng-doc-list-control-box[data-ng-doc-invalid=true],.ng-doc-list-control-box[data-ng-doc-invalid=true]:hover{border-color:var(--ng-doc-alert)}.ng-doc-list-control-box[data-ng-doc-disabled=true]{cursor:default;opacity:.6}.ng-doc-list-control-flow{display:flex;flex:1 1 auto;flex-wrap:wrap;align-items:center;gap:4px;min-width:0}.ng-doc-list-control-chips{display:contents;margin:0;padding:0;list-style:none}.ng-doc-list-control-item{display:flex;min-width:0;max-width:100%}.ng-doc-list-control-item[data-ng-doc-dragging=true] .ng-doc-list-control-chip{opacity:.5}.ng-doc-list-control-chip{gap:2px;height:24px;max-width:100%;padding:0 2px 0 0;overflow:hidden;--ng-doc-font-size: 13px;--ng-doc-line-height: 24px}.ng-doc-list-control-chip-text{min-width:0;overflow:hidden;padding-left:var(--ng-doc-space-2);border-radius:var(--ng-doc-radius-sm);text-overflow:ellipsis;outline:none;cursor:grab;-webkit-user-select:none;user-select:none;touch-action:none}.ng-doc-list-control-chip-text:focus-visible{box-shadow:var(--ng-doc-focus-ring)}[data-ng-doc-disabled=true] .ng-doc-list-control-chip-text{cursor:default}.ng-doc-list-control-empty-string{font-family:var(--ng-doc-code-font);opacity:.7}.ng-doc-list-control-remove{display:inline-flex;flex:none;align-items:center;justify-content:center;width:18px;height:18px;margin:0;padding:0;border:0;border-radius:var(--ng-doc-radius-sm);background:transparent;color:inherit;cursor:pointer;opacity:.6;--ng-doc-icon-width: 12px;--ng-doc-icon-height: 12px}.ng-doc-list-control-remove ::ng-deep svg{width:12px;height:12px}.ng-doc-list-control-remove:hover:not(:disabled){opacity:1;background:var(--ng-doc-hover-background)}.ng-doc-list-control-remove:disabled{cursor:default}.ng-doc-list-control-edit{box-sizing:content-box;height:22px;min-width:2ch;max-width:100%;margin:0;padding:0 var(--ng-doc-space-2);border:1px solid var(--ng-doc-primary);border-radius:var(--ng-doc-radius-sm);outline:none;background:var(--ng-doc-input-background-color);color:var(--ng-doc-text);font:inherit;font-size:13px}.ng-doc-list-control-edit[aria-invalid=true]{border-color:var(--ng-doc-alert)}.ng-doc-list-control-literal{padding:0 var(--ng-doc-space-1);font-family:var(--ng-doc-code-font);font-size:13px;line-height:24px;color:var(--ng-doc-text-muted)}.ng-doc-list-control-field{flex:1 1 32px;min-width:32px;height:24px;margin:0;padding:0 var(--ng-doc-space-1);border:0;outline:none;background:transparent;color:var(--ng-doc-text);font-family:var(--ng-doc-font-family);font-size:14px}.ng-doc-list-control-field::placeholder{color:var(--ng-doc-text-muted)}.ng-doc-list-control-reset{flex:none}.ng-doc-list-control-dropdown{display:contents}.ng-doc-list-control-error{font-size:12px;line-height:16px;color:var(--ng-doc-alert)}.ng-doc-list-control-status{position:absolute;width:1px;height:1px;overflow:hidden;clip-path:inset(50%);white-space:nowrap}.ng-doc-list-control-panel{box-sizing:border-box;min-width:160px;max-width:100vw;padding:var(--ng-doc-space-1) 0;font-family:var(--ng-doc-font-family);font-size:14px;line-height:20px}.ng-doc-list-control-row{display:flex;align-items:center;gap:var(--ng-doc-space-2);padding:4px calc(var(--ng-doc-base-gutter) * 1.5);color:var(--ng-doc-text);cursor:pointer}.ng-doc-list-control-row[data-ng-doc-highlighted=true]{background:var(--ng-doc-hover-background)}.ng-doc-list-control-row[data-ng-doc-list-row=add]{color:var(--ng-doc-heading-color);font-weight:500}.ng-doc-list-control-row[aria-selected=true]:not([data-ng-doc-list-row=add]){color:var(--ng-doc-heading-color)}.ng-doc-list-control-row-text{flex:1 1 auto;min-width:0;overflow:hidden;white-space:nowrap;text-overflow:ellipsis}.ng-doc-list-control-check{flex:none;--ng-doc-icon-color: var(--ng-doc-primary)}.ng-doc-list-control-no-matches{padding:4px calc(var(--ng-doc-base-gutter) * 1.5);color:var(--ng-doc-text-muted)}\n"], dependencies: [{ kind: "component", type: NgDocButtonIconComponent, selector: "button[ng-doc-button-icon], a[ng-doc-button-icon], button[ng-doc-button-icon-raised], a[ng-doc-button-icon-raised], button[ng-doc-button-icon-transparent], a[ng-doc-button-icon-transparent]", inputs: ["size", "rounded"] }, { kind: "component", type: NgDocDropdownComponent, selector: "ng-doc-dropdown", inputs: ["content", "origin", "closeIfOutsideClick", "closeIfInnerClick", "withArrow", "borderOffset", "panelClass", "contactBorder", "hasBackdrop", "positions", "minHeight", "maxHeight", "height", "minWidth", "maxWidth", "width"], outputs: ["beforeOpen", "afterOpen", "beforeClose", "afterClose"] }, { kind: "directive", type: NgDocDropdownOriginDirective, selector: "[ngDocDropdownOrigin]", exportAs: ["ngDocDropdownOrigin"] }, { kind: "component", type: NgDocIconComponent, selector: "ng-doc-icon", inputs: ["icon", "customIcon", "size"] }, { kind: "component", type: NgDocTagComponent, selector: "ng-doc-tag", inputs: ["color", "size", "mod"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
672
|
+
}
|
|
673
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.2.1", ngImport: i0, type: NgDocStringListControlComponent, decorators: [{
|
|
674
|
+
type: Component,
|
|
675
|
+
args: [{ selector: 'ng-doc-string-list-control', changeDetection: ChangeDetectionStrategy.OnPush, imports: LIST_CONTROL_IMPORTS, template: "<!-- The chips and the text field share one field-styled box, which the dropdown is attached to. -->\n<div\n #fieldBox\n class=\"ng-doc-list-control-box\"\n ngDocDropdownOrigin\n [attr.data-ng-doc-invalid]=\"invalid() || editInvalid()\"\n [attr.data-ng-doc-disabled]=\"disabled\"\n (click)=\"fieldClick($event, field)\">\n <div class=\"ng-doc-list-control-flow\">\n <!-- `display: contents` lets the chips and the text field wrap as one line of content. -->\n <ul class=\"ng-doc-list-control-chips\" role=\"list\">\n @for (item of shownItems(); track $index) {\n <li\n class=\"ng-doc-list-control-item\"\n [attr.data-ng-doc-list-item]=\"$index\"\n [attr.data-ng-doc-dragging]=\"dragIndex() === $index\">\n @if (editing() === $index) {\n <input\n class=\"ng-doc-list-control-edit\"\n data-ng-doc-list-edit\n spellcheck=\"false\"\n autocomplete=\"off\"\n [size]=\"editText().length || 1\"\n [value]=\"editText()\"\n [attr.aria-label]=\"texts.editItem({ item: chipText(item) })\"\n [attr.aria-invalid]=\"editInvalid() ? 'true' : null\"\n [attr.aria-describedby]=\"editInvalid() ? id + '-error' : null\"\n (input)=\"editText.set(edit.value)\"\n (keydown)=\"editKeydown($event)\"\n (blur)=\"editBlur()\"\n #edit />\n } @else {\n <ng-doc-tag class=\"ng-doc-list-control-chip\" mod=\"light\">\n <span\n class=\"ng-doc-list-control-chip-text\"\n data-ng-doc-list-chip\n [class.ng-doc-list-control-empty-string]=\"labelOf(item) === ''\"\n [attr.role]=\"freeText ? 'button' : null\"\n [attr.tabindex]=\"\n disabled ? null : $index === tabStop() || ($last && tabStop() > $index) ? 0 : -1\n \"\n [attr.aria-describedby]=\"\n freeText ? id + '-edit-keys ' + id + '-keys' : id + '-keys'\n \"\n (focus)=\"tabStop.set($index)\"\n (keydown)=\"chipKeydown($event, $index)\"\n (dblclick)=\"startEdit($index)\"\n (pointerdown)=\"chipPointerDown($event, $index)\"\n (pointermove)=\"chipPointerMove($event)\"\n (pointerup)=\"chipPointerUp($event)\"\n (pointercancel)=\"cancelDrag()\"\n >{{ chipText(item) }}</span\n >\n <button\n type=\"button\"\n class=\"ng-doc-list-control-remove\"\n data-ng-doc-list-remove\n tabindex=\"-1\"\n [attr.aria-label]=\"texts.removeItem({ item: chipText(item) })\"\n [disabled]=\"disabled\"\n (click)=\"remove($index)\">\n <ng-doc-icon icon=\"x\" aria-hidden=\"true\"></ng-doc-icon>\n </button>\n </ng-doc-tag>\n }\n </li>\n }\n </ul>\n @if (literal(); as literal) {\n <code class=\"ng-doc-list-control-literal\" data-ng-doc-list-literal>{{ literal }}</code>\n }\n <input\n #field\n class=\"ng-doc-list-control-field\"\n data-ng-doc-list-field\n role=\"combobox\"\n aria-autocomplete=\"list\"\n spellcheck=\"false\"\n autocomplete=\"off\"\n [value]=\"text()\"\n [placeholder]=\"items().length ? '' : texts.addItem\"\n [disabled]=\"disabled\"\n [attr.aria-label]=\"texts.addItem\"\n [attr.aria-expanded]=\"panelOpen()\"\n [attr.aria-controls]=\"id + '-rows'\"\n [attr.aria-activedescendant]=\"panelOpen() && highlighted() >= 0 ? rowId(highlighted()) : null\"\n [attr.aria-invalid]=\"invalid() ? 'true' : null\"\n [attr.aria-describedby]=\"invalid() ? id + '-error' : null\"\n (input)=\"type(field.value)\"\n (keydown)=\"fieldKeydown($event, field)\" />\n </div>\n @if (canReset()) {\n <button\n ng-doc-button-icon-transparent\n type=\"button\"\n size=\"small\"\n class=\"ng-doc-list-control-reset\"\n data-ng-doc-list-reset\n [rounded]=\"false\"\n [attr.aria-label]=\"texts.resetToDefault\"\n [disabled]=\"disabled\"\n (click)=\"reset()\">\n <ng-doc-icon icon=\"x\" aria-hidden=\"true\"></ng-doc-icon>\n </button>\n }\n <!-- The dropdown's host takes the focus while it is open, to move it into the overlay. The rows\n here are never focused (the text field names the highlighted one), so the host is inert. -->\n <span class=\"ng-doc-list-control-dropdown\" inert>\n <ng-doc-dropdown\n [content]=\"panel\"\n [positions]=\"['bottom-center', 'top-center']\"\n [maxHeight]=\"240\"\n (beforeClose)=\"expanded.set(false)\"></ng-doc-dropdown>\n </span>\n</div>\n@if (invalid() || editInvalid()) {\n <span class=\"ng-doc-list-control-error\" [id]=\"id + '-error'\">{{ texts.invalidNumber }}</span>\n}\n<span hidden [id]=\"id + '-keys'\">{{ texts.itemKeys }}</span>\n<span hidden [id]=\"id + '-edit-keys'\">{{ texts.editKeys }}</span>\n<span class=\"ng-doc-list-control-status\" role=\"status\">{{ announcement() }}</span>\n\n<ng-template #panel>\n <!-- A press on a row keeps the focus in the text field. -->\n <div\n class=\"ng-doc-list-control-panel\"\n [style.width.px]=\"panelWidth()\"\n (mousedown)=\"$event.preventDefault()\">\n @if (rows().length) {\n <div\n role=\"listbox\"\n class=\"ng-doc-list-control-rows\"\n [id]=\"id + '-rows'\"\n [attr.aria-label]=\"texts.suggestions\"\n [attr.aria-multiselectable]=\"freeText ? null : 'true'\">\n @for (row of rows(); track $index) {\n <div\n role=\"option\"\n class=\"ng-doc-list-control-row\"\n [id]=\"rowId($index)\"\n [attr.data-ng-doc-list-row]=\"row.kind\"\n [attr.data-ng-doc-highlighted]=\"highlighted() === $index\"\n [attr.aria-selected]=\"freeText ? highlighted() === $index : row.selected\"\n (pointerenter)=\"highlighted.set($index)\"\n (click)=\"pick(row)\">\n <span class=\"ng-doc-list-control-row-text\">\n {{ row.kind === 'add' ? texts.addValue({ value: row.label }) : row.label || '\"\"' }}\n </span>\n @if (row.selected) {\n <ng-doc-icon\n class=\"ng-doc-list-control-check\"\n icon=\"check\"\n aria-hidden=\"true\"></ng-doc-icon>\n }\n </div>\n }\n </div>\n } @else {\n <div class=\"ng-doc-list-control-no-matches\" role=\"status\">{{ texts.noMatches }}</div>\n }\n </div>\n</ng-template>\n", styles: [":host{display:flex;flex-direction:column;gap:var(--ng-doc-space-1);width:100%;min-width:0}.ng-doc-list-control-box{display:flex;align-items:flex-start;box-sizing:border-box;width:100%;min-width:0;min-height:var(--ng-doc-input-height);padding:5px var(--ng-doc-space-1) 5px 5px;border:var(--ng-doc-input-border);border-radius:var(--ng-doc-floated-border-radius);background-color:var(--ng-doc-input-background-color);cursor:text}.ng-doc-list-control-box:hover:not([data-ng-doc-disabled=true]){border:var(--ng-doc-input-border-hover)}.ng-doc-list-control-box:has(:focus-visible){box-shadow:var(--ng-doc-focus-ring)}.ng-doc-list-control-box[data-ng-doc-invalid=true],.ng-doc-list-control-box[data-ng-doc-invalid=true]:hover{border-color:var(--ng-doc-alert)}.ng-doc-list-control-box[data-ng-doc-disabled=true]{cursor:default;opacity:.6}.ng-doc-list-control-flow{display:flex;flex:1 1 auto;flex-wrap:wrap;align-items:center;gap:4px;min-width:0}.ng-doc-list-control-chips{display:contents;margin:0;padding:0;list-style:none}.ng-doc-list-control-item{display:flex;min-width:0;max-width:100%}.ng-doc-list-control-item[data-ng-doc-dragging=true] .ng-doc-list-control-chip{opacity:.5}.ng-doc-list-control-chip{gap:2px;height:24px;max-width:100%;padding:0 2px 0 0;overflow:hidden;--ng-doc-font-size: 13px;--ng-doc-line-height: 24px}.ng-doc-list-control-chip-text{min-width:0;overflow:hidden;padding-left:var(--ng-doc-space-2);border-radius:var(--ng-doc-radius-sm);text-overflow:ellipsis;outline:none;cursor:grab;-webkit-user-select:none;user-select:none;touch-action:none}.ng-doc-list-control-chip-text:focus-visible{box-shadow:var(--ng-doc-focus-ring)}[data-ng-doc-disabled=true] .ng-doc-list-control-chip-text{cursor:default}.ng-doc-list-control-empty-string{font-family:var(--ng-doc-code-font);opacity:.7}.ng-doc-list-control-remove{display:inline-flex;flex:none;align-items:center;justify-content:center;width:18px;height:18px;margin:0;padding:0;border:0;border-radius:var(--ng-doc-radius-sm);background:transparent;color:inherit;cursor:pointer;opacity:.6;--ng-doc-icon-width: 12px;--ng-doc-icon-height: 12px}.ng-doc-list-control-remove ::ng-deep svg{width:12px;height:12px}.ng-doc-list-control-remove:hover:not(:disabled){opacity:1;background:var(--ng-doc-hover-background)}.ng-doc-list-control-remove:disabled{cursor:default}.ng-doc-list-control-edit{box-sizing:content-box;height:22px;min-width:2ch;max-width:100%;margin:0;padding:0 var(--ng-doc-space-2);border:1px solid var(--ng-doc-primary);border-radius:var(--ng-doc-radius-sm);outline:none;background:var(--ng-doc-input-background-color);color:var(--ng-doc-text);font:inherit;font-size:13px}.ng-doc-list-control-edit[aria-invalid=true]{border-color:var(--ng-doc-alert)}.ng-doc-list-control-literal{padding:0 var(--ng-doc-space-1);font-family:var(--ng-doc-code-font);font-size:13px;line-height:24px;color:var(--ng-doc-text-muted)}.ng-doc-list-control-field{flex:1 1 32px;min-width:32px;height:24px;margin:0;padding:0 var(--ng-doc-space-1);border:0;outline:none;background:transparent;color:var(--ng-doc-text);font-family:var(--ng-doc-font-family);font-size:14px}.ng-doc-list-control-field::placeholder{color:var(--ng-doc-text-muted)}.ng-doc-list-control-reset{flex:none}.ng-doc-list-control-dropdown{display:contents}.ng-doc-list-control-error{font-size:12px;line-height:16px;color:var(--ng-doc-alert)}.ng-doc-list-control-status{position:absolute;width:1px;height:1px;overflow:hidden;clip-path:inset(50%);white-space:nowrap}.ng-doc-list-control-panel{box-sizing:border-box;min-width:160px;max-width:100vw;padding:var(--ng-doc-space-1) 0;font-family:var(--ng-doc-font-family);font-size:14px;line-height:20px}.ng-doc-list-control-row{display:flex;align-items:center;gap:var(--ng-doc-space-2);padding:4px calc(var(--ng-doc-base-gutter) * 1.5);color:var(--ng-doc-text);cursor:pointer}.ng-doc-list-control-row[data-ng-doc-highlighted=true]{background:var(--ng-doc-hover-background)}.ng-doc-list-control-row[data-ng-doc-list-row=add]{color:var(--ng-doc-heading-color);font-weight:500}.ng-doc-list-control-row[aria-selected=true]:not([data-ng-doc-list-row=add]){color:var(--ng-doc-heading-color)}.ng-doc-list-control-row-text{flex:1 1 auto;min-width:0;overflow:hidden;white-space:nowrap;text-overflow:ellipsis}.ng-doc-list-control-check{flex:none;--ng-doc-icon-color: var(--ng-doc-primary)}.ng-doc-list-control-no-matches{padding:4px calc(var(--ng-doc-base-gutter) * 1.5);color:var(--ng-doc-text-muted)}\n"] }]
|
|
676
|
+
}], ctorParameters: () => [] });
|
|
677
|
+
/**
|
|
678
|
+
* The playground control of `number[]` inputs: a chip combobox (`NgDocListControl`) whose items are
|
|
679
|
+
* numbers. Text that is not a number marks the field as invalid and adds nothing; a chip edited to
|
|
680
|
+
* such text keeps its number.
|
|
681
|
+
*/
|
|
682
|
+
class NgDocNumberListControlComponent extends NgDocListControl {
|
|
683
|
+
constructor() {
|
|
684
|
+
super();
|
|
685
|
+
this.freeText = true;
|
|
686
|
+
}
|
|
687
|
+
parseItem(text) {
|
|
688
|
+
const value = Number(text);
|
|
689
|
+
return text.trim() !== '' && Number.isFinite(value) ? value : undefined;
|
|
690
|
+
}
|
|
691
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.2.1", ngImport: i0, type: NgDocNumberListControlComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
692
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.2.1", type: NgDocNumberListControlComponent, isStandalone: true, selector: "ng-doc-number-list-control", usesInheritance: true, ngImport: i0, template: "<!-- The chips and the text field share one field-styled box, which the dropdown is attached to. -->\n<div\n #fieldBox\n class=\"ng-doc-list-control-box\"\n ngDocDropdownOrigin\n [attr.data-ng-doc-invalid]=\"invalid() || editInvalid()\"\n [attr.data-ng-doc-disabled]=\"disabled\"\n (click)=\"fieldClick($event, field)\">\n <div class=\"ng-doc-list-control-flow\">\n <!-- `display: contents` lets the chips and the text field wrap as one line of content. -->\n <ul class=\"ng-doc-list-control-chips\" role=\"list\">\n @for (item of shownItems(); track $index) {\n <li\n class=\"ng-doc-list-control-item\"\n [attr.data-ng-doc-list-item]=\"$index\"\n [attr.data-ng-doc-dragging]=\"dragIndex() === $index\">\n @if (editing() === $index) {\n <input\n class=\"ng-doc-list-control-edit\"\n data-ng-doc-list-edit\n spellcheck=\"false\"\n autocomplete=\"off\"\n [size]=\"editText().length || 1\"\n [value]=\"editText()\"\n [attr.aria-label]=\"texts.editItem({ item: chipText(item) })\"\n [attr.aria-invalid]=\"editInvalid() ? 'true' : null\"\n [attr.aria-describedby]=\"editInvalid() ? id + '-error' : null\"\n (input)=\"editText.set(edit.value)\"\n (keydown)=\"editKeydown($event)\"\n (blur)=\"editBlur()\"\n #edit />\n } @else {\n <ng-doc-tag class=\"ng-doc-list-control-chip\" mod=\"light\">\n <span\n class=\"ng-doc-list-control-chip-text\"\n data-ng-doc-list-chip\n [class.ng-doc-list-control-empty-string]=\"labelOf(item) === ''\"\n [attr.role]=\"freeText ? 'button' : null\"\n [attr.tabindex]=\"\n disabled ? null : $index === tabStop() || ($last && tabStop() > $index) ? 0 : -1\n \"\n [attr.aria-describedby]=\"\n freeText ? id + '-edit-keys ' + id + '-keys' : id + '-keys'\n \"\n (focus)=\"tabStop.set($index)\"\n (keydown)=\"chipKeydown($event, $index)\"\n (dblclick)=\"startEdit($index)\"\n (pointerdown)=\"chipPointerDown($event, $index)\"\n (pointermove)=\"chipPointerMove($event)\"\n (pointerup)=\"chipPointerUp($event)\"\n (pointercancel)=\"cancelDrag()\"\n >{{ chipText(item) }}</span\n >\n <button\n type=\"button\"\n class=\"ng-doc-list-control-remove\"\n data-ng-doc-list-remove\n tabindex=\"-1\"\n [attr.aria-label]=\"texts.removeItem({ item: chipText(item) })\"\n [disabled]=\"disabled\"\n (click)=\"remove($index)\">\n <ng-doc-icon icon=\"x\" aria-hidden=\"true\"></ng-doc-icon>\n </button>\n </ng-doc-tag>\n }\n </li>\n }\n </ul>\n @if (literal(); as literal) {\n <code class=\"ng-doc-list-control-literal\" data-ng-doc-list-literal>{{ literal }}</code>\n }\n <input\n #field\n class=\"ng-doc-list-control-field\"\n data-ng-doc-list-field\n role=\"combobox\"\n aria-autocomplete=\"list\"\n spellcheck=\"false\"\n autocomplete=\"off\"\n [value]=\"text()\"\n [placeholder]=\"items().length ? '' : texts.addItem\"\n [disabled]=\"disabled\"\n [attr.aria-label]=\"texts.addItem\"\n [attr.aria-expanded]=\"panelOpen()\"\n [attr.aria-controls]=\"id + '-rows'\"\n [attr.aria-activedescendant]=\"panelOpen() && highlighted() >= 0 ? rowId(highlighted()) : null\"\n [attr.aria-invalid]=\"invalid() ? 'true' : null\"\n [attr.aria-describedby]=\"invalid() ? id + '-error' : null\"\n (input)=\"type(field.value)\"\n (keydown)=\"fieldKeydown($event, field)\" />\n </div>\n @if (canReset()) {\n <button\n ng-doc-button-icon-transparent\n type=\"button\"\n size=\"small\"\n class=\"ng-doc-list-control-reset\"\n data-ng-doc-list-reset\n [rounded]=\"false\"\n [attr.aria-label]=\"texts.resetToDefault\"\n [disabled]=\"disabled\"\n (click)=\"reset()\">\n <ng-doc-icon icon=\"x\" aria-hidden=\"true\"></ng-doc-icon>\n </button>\n }\n <!-- The dropdown's host takes the focus while it is open, to move it into the overlay. The rows\n here are never focused (the text field names the highlighted one), so the host is inert. -->\n <span class=\"ng-doc-list-control-dropdown\" inert>\n <ng-doc-dropdown\n [content]=\"panel\"\n [positions]=\"['bottom-center', 'top-center']\"\n [maxHeight]=\"240\"\n (beforeClose)=\"expanded.set(false)\"></ng-doc-dropdown>\n </span>\n</div>\n@if (invalid() || editInvalid()) {\n <span class=\"ng-doc-list-control-error\" [id]=\"id + '-error'\">{{ texts.invalidNumber }}</span>\n}\n<span hidden [id]=\"id + '-keys'\">{{ texts.itemKeys }}</span>\n<span hidden [id]=\"id + '-edit-keys'\">{{ texts.editKeys }}</span>\n<span class=\"ng-doc-list-control-status\" role=\"status\">{{ announcement() }}</span>\n\n<ng-template #panel>\n <!-- A press on a row keeps the focus in the text field. -->\n <div\n class=\"ng-doc-list-control-panel\"\n [style.width.px]=\"panelWidth()\"\n (mousedown)=\"$event.preventDefault()\">\n @if (rows().length) {\n <div\n role=\"listbox\"\n class=\"ng-doc-list-control-rows\"\n [id]=\"id + '-rows'\"\n [attr.aria-label]=\"texts.suggestions\"\n [attr.aria-multiselectable]=\"freeText ? null : 'true'\">\n @for (row of rows(); track $index) {\n <div\n role=\"option\"\n class=\"ng-doc-list-control-row\"\n [id]=\"rowId($index)\"\n [attr.data-ng-doc-list-row]=\"row.kind\"\n [attr.data-ng-doc-highlighted]=\"highlighted() === $index\"\n [attr.aria-selected]=\"freeText ? highlighted() === $index : row.selected\"\n (pointerenter)=\"highlighted.set($index)\"\n (click)=\"pick(row)\">\n <span class=\"ng-doc-list-control-row-text\">\n {{ row.kind === 'add' ? texts.addValue({ value: row.label }) : row.label || '\"\"' }}\n </span>\n @if (row.selected) {\n <ng-doc-icon\n class=\"ng-doc-list-control-check\"\n icon=\"check\"\n aria-hidden=\"true\"></ng-doc-icon>\n }\n </div>\n }\n </div>\n } @else {\n <div class=\"ng-doc-list-control-no-matches\" role=\"status\">{{ texts.noMatches }}</div>\n }\n </div>\n</ng-template>\n", styles: [":host{display:flex;flex-direction:column;gap:var(--ng-doc-space-1);width:100%;min-width:0}.ng-doc-list-control-box{display:flex;align-items:flex-start;box-sizing:border-box;width:100%;min-width:0;min-height:var(--ng-doc-input-height);padding:5px var(--ng-doc-space-1) 5px 5px;border:var(--ng-doc-input-border);border-radius:var(--ng-doc-floated-border-radius);background-color:var(--ng-doc-input-background-color);cursor:text}.ng-doc-list-control-box:hover:not([data-ng-doc-disabled=true]){border:var(--ng-doc-input-border-hover)}.ng-doc-list-control-box:has(:focus-visible){box-shadow:var(--ng-doc-focus-ring)}.ng-doc-list-control-box[data-ng-doc-invalid=true],.ng-doc-list-control-box[data-ng-doc-invalid=true]:hover{border-color:var(--ng-doc-alert)}.ng-doc-list-control-box[data-ng-doc-disabled=true]{cursor:default;opacity:.6}.ng-doc-list-control-flow{display:flex;flex:1 1 auto;flex-wrap:wrap;align-items:center;gap:4px;min-width:0}.ng-doc-list-control-chips{display:contents;margin:0;padding:0;list-style:none}.ng-doc-list-control-item{display:flex;min-width:0;max-width:100%}.ng-doc-list-control-item[data-ng-doc-dragging=true] .ng-doc-list-control-chip{opacity:.5}.ng-doc-list-control-chip{gap:2px;height:24px;max-width:100%;padding:0 2px 0 0;overflow:hidden;--ng-doc-font-size: 13px;--ng-doc-line-height: 24px}.ng-doc-list-control-chip-text{min-width:0;overflow:hidden;padding-left:var(--ng-doc-space-2);border-radius:var(--ng-doc-radius-sm);text-overflow:ellipsis;outline:none;cursor:grab;-webkit-user-select:none;user-select:none;touch-action:none}.ng-doc-list-control-chip-text:focus-visible{box-shadow:var(--ng-doc-focus-ring)}[data-ng-doc-disabled=true] .ng-doc-list-control-chip-text{cursor:default}.ng-doc-list-control-empty-string{font-family:var(--ng-doc-code-font);opacity:.7}.ng-doc-list-control-remove{display:inline-flex;flex:none;align-items:center;justify-content:center;width:18px;height:18px;margin:0;padding:0;border:0;border-radius:var(--ng-doc-radius-sm);background:transparent;color:inherit;cursor:pointer;opacity:.6;--ng-doc-icon-width: 12px;--ng-doc-icon-height: 12px}.ng-doc-list-control-remove ::ng-deep svg{width:12px;height:12px}.ng-doc-list-control-remove:hover:not(:disabled){opacity:1;background:var(--ng-doc-hover-background)}.ng-doc-list-control-remove:disabled{cursor:default}.ng-doc-list-control-edit{box-sizing:content-box;height:22px;min-width:2ch;max-width:100%;margin:0;padding:0 var(--ng-doc-space-2);border:1px solid var(--ng-doc-primary);border-radius:var(--ng-doc-radius-sm);outline:none;background:var(--ng-doc-input-background-color);color:var(--ng-doc-text);font:inherit;font-size:13px}.ng-doc-list-control-edit[aria-invalid=true]{border-color:var(--ng-doc-alert)}.ng-doc-list-control-literal{padding:0 var(--ng-doc-space-1);font-family:var(--ng-doc-code-font);font-size:13px;line-height:24px;color:var(--ng-doc-text-muted)}.ng-doc-list-control-field{flex:1 1 32px;min-width:32px;height:24px;margin:0;padding:0 var(--ng-doc-space-1);border:0;outline:none;background:transparent;color:var(--ng-doc-text);font-family:var(--ng-doc-font-family);font-size:14px}.ng-doc-list-control-field::placeholder{color:var(--ng-doc-text-muted)}.ng-doc-list-control-reset{flex:none}.ng-doc-list-control-dropdown{display:contents}.ng-doc-list-control-error{font-size:12px;line-height:16px;color:var(--ng-doc-alert)}.ng-doc-list-control-status{position:absolute;width:1px;height:1px;overflow:hidden;clip-path:inset(50%);white-space:nowrap}.ng-doc-list-control-panel{box-sizing:border-box;min-width:160px;max-width:100vw;padding:var(--ng-doc-space-1) 0;font-family:var(--ng-doc-font-family);font-size:14px;line-height:20px}.ng-doc-list-control-row{display:flex;align-items:center;gap:var(--ng-doc-space-2);padding:4px calc(var(--ng-doc-base-gutter) * 1.5);color:var(--ng-doc-text);cursor:pointer}.ng-doc-list-control-row[data-ng-doc-highlighted=true]{background:var(--ng-doc-hover-background)}.ng-doc-list-control-row[data-ng-doc-list-row=add]{color:var(--ng-doc-heading-color);font-weight:500}.ng-doc-list-control-row[aria-selected=true]:not([data-ng-doc-list-row=add]){color:var(--ng-doc-heading-color)}.ng-doc-list-control-row-text{flex:1 1 auto;min-width:0;overflow:hidden;white-space:nowrap;text-overflow:ellipsis}.ng-doc-list-control-check{flex:none;--ng-doc-icon-color: var(--ng-doc-primary)}.ng-doc-list-control-no-matches{padding:4px calc(var(--ng-doc-base-gutter) * 1.5);color:var(--ng-doc-text-muted)}\n"], dependencies: [{ kind: "component", type: NgDocButtonIconComponent, selector: "button[ng-doc-button-icon], a[ng-doc-button-icon], button[ng-doc-button-icon-raised], a[ng-doc-button-icon-raised], button[ng-doc-button-icon-transparent], a[ng-doc-button-icon-transparent]", inputs: ["size", "rounded"] }, { kind: "component", type: NgDocDropdownComponent, selector: "ng-doc-dropdown", inputs: ["content", "origin", "closeIfOutsideClick", "closeIfInnerClick", "withArrow", "borderOffset", "panelClass", "contactBorder", "hasBackdrop", "positions", "minHeight", "maxHeight", "height", "minWidth", "maxWidth", "width"], outputs: ["beforeOpen", "afterOpen", "beforeClose", "afterClose"] }, { kind: "directive", type: NgDocDropdownOriginDirective, selector: "[ngDocDropdownOrigin]", exportAs: ["ngDocDropdownOrigin"] }, { kind: "component", type: NgDocIconComponent, selector: "ng-doc-icon", inputs: ["icon", "customIcon", "size"] }, { kind: "component", type: NgDocTagComponent, selector: "ng-doc-tag", inputs: ["color", "size", "mod"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
693
|
+
}
|
|
694
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.2.1", ngImport: i0, type: NgDocNumberListControlComponent, decorators: [{
|
|
695
|
+
type: Component,
|
|
696
|
+
args: [{ selector: 'ng-doc-number-list-control', changeDetection: ChangeDetectionStrategy.OnPush, imports: LIST_CONTROL_IMPORTS, template: "<!-- The chips and the text field share one field-styled box, which the dropdown is attached to. -->\n<div\n #fieldBox\n class=\"ng-doc-list-control-box\"\n ngDocDropdownOrigin\n [attr.data-ng-doc-invalid]=\"invalid() || editInvalid()\"\n [attr.data-ng-doc-disabled]=\"disabled\"\n (click)=\"fieldClick($event, field)\">\n <div class=\"ng-doc-list-control-flow\">\n <!-- `display: contents` lets the chips and the text field wrap as one line of content. -->\n <ul class=\"ng-doc-list-control-chips\" role=\"list\">\n @for (item of shownItems(); track $index) {\n <li\n class=\"ng-doc-list-control-item\"\n [attr.data-ng-doc-list-item]=\"$index\"\n [attr.data-ng-doc-dragging]=\"dragIndex() === $index\">\n @if (editing() === $index) {\n <input\n class=\"ng-doc-list-control-edit\"\n data-ng-doc-list-edit\n spellcheck=\"false\"\n autocomplete=\"off\"\n [size]=\"editText().length || 1\"\n [value]=\"editText()\"\n [attr.aria-label]=\"texts.editItem({ item: chipText(item) })\"\n [attr.aria-invalid]=\"editInvalid() ? 'true' : null\"\n [attr.aria-describedby]=\"editInvalid() ? id + '-error' : null\"\n (input)=\"editText.set(edit.value)\"\n (keydown)=\"editKeydown($event)\"\n (blur)=\"editBlur()\"\n #edit />\n } @else {\n <ng-doc-tag class=\"ng-doc-list-control-chip\" mod=\"light\">\n <span\n class=\"ng-doc-list-control-chip-text\"\n data-ng-doc-list-chip\n [class.ng-doc-list-control-empty-string]=\"labelOf(item) === ''\"\n [attr.role]=\"freeText ? 'button' : null\"\n [attr.tabindex]=\"\n disabled ? null : $index === tabStop() || ($last && tabStop() > $index) ? 0 : -1\n \"\n [attr.aria-describedby]=\"\n freeText ? id + '-edit-keys ' + id + '-keys' : id + '-keys'\n \"\n (focus)=\"tabStop.set($index)\"\n (keydown)=\"chipKeydown($event, $index)\"\n (dblclick)=\"startEdit($index)\"\n (pointerdown)=\"chipPointerDown($event, $index)\"\n (pointermove)=\"chipPointerMove($event)\"\n (pointerup)=\"chipPointerUp($event)\"\n (pointercancel)=\"cancelDrag()\"\n >{{ chipText(item) }}</span\n >\n <button\n type=\"button\"\n class=\"ng-doc-list-control-remove\"\n data-ng-doc-list-remove\n tabindex=\"-1\"\n [attr.aria-label]=\"texts.removeItem({ item: chipText(item) })\"\n [disabled]=\"disabled\"\n (click)=\"remove($index)\">\n <ng-doc-icon icon=\"x\" aria-hidden=\"true\"></ng-doc-icon>\n </button>\n </ng-doc-tag>\n }\n </li>\n }\n </ul>\n @if (literal(); as literal) {\n <code class=\"ng-doc-list-control-literal\" data-ng-doc-list-literal>{{ literal }}</code>\n }\n <input\n #field\n class=\"ng-doc-list-control-field\"\n data-ng-doc-list-field\n role=\"combobox\"\n aria-autocomplete=\"list\"\n spellcheck=\"false\"\n autocomplete=\"off\"\n [value]=\"text()\"\n [placeholder]=\"items().length ? '' : texts.addItem\"\n [disabled]=\"disabled\"\n [attr.aria-label]=\"texts.addItem\"\n [attr.aria-expanded]=\"panelOpen()\"\n [attr.aria-controls]=\"id + '-rows'\"\n [attr.aria-activedescendant]=\"panelOpen() && highlighted() >= 0 ? rowId(highlighted()) : null\"\n [attr.aria-invalid]=\"invalid() ? 'true' : null\"\n [attr.aria-describedby]=\"invalid() ? id + '-error' : null\"\n (input)=\"type(field.value)\"\n (keydown)=\"fieldKeydown($event, field)\" />\n </div>\n @if (canReset()) {\n <button\n ng-doc-button-icon-transparent\n type=\"button\"\n size=\"small\"\n class=\"ng-doc-list-control-reset\"\n data-ng-doc-list-reset\n [rounded]=\"false\"\n [attr.aria-label]=\"texts.resetToDefault\"\n [disabled]=\"disabled\"\n (click)=\"reset()\">\n <ng-doc-icon icon=\"x\" aria-hidden=\"true\"></ng-doc-icon>\n </button>\n }\n <!-- The dropdown's host takes the focus while it is open, to move it into the overlay. The rows\n here are never focused (the text field names the highlighted one), so the host is inert. -->\n <span class=\"ng-doc-list-control-dropdown\" inert>\n <ng-doc-dropdown\n [content]=\"panel\"\n [positions]=\"['bottom-center', 'top-center']\"\n [maxHeight]=\"240\"\n (beforeClose)=\"expanded.set(false)\"></ng-doc-dropdown>\n </span>\n</div>\n@if (invalid() || editInvalid()) {\n <span class=\"ng-doc-list-control-error\" [id]=\"id + '-error'\">{{ texts.invalidNumber }}</span>\n}\n<span hidden [id]=\"id + '-keys'\">{{ texts.itemKeys }}</span>\n<span hidden [id]=\"id + '-edit-keys'\">{{ texts.editKeys }}</span>\n<span class=\"ng-doc-list-control-status\" role=\"status\">{{ announcement() }}</span>\n\n<ng-template #panel>\n <!-- A press on a row keeps the focus in the text field. -->\n <div\n class=\"ng-doc-list-control-panel\"\n [style.width.px]=\"panelWidth()\"\n (mousedown)=\"$event.preventDefault()\">\n @if (rows().length) {\n <div\n role=\"listbox\"\n class=\"ng-doc-list-control-rows\"\n [id]=\"id + '-rows'\"\n [attr.aria-label]=\"texts.suggestions\"\n [attr.aria-multiselectable]=\"freeText ? null : 'true'\">\n @for (row of rows(); track $index) {\n <div\n role=\"option\"\n class=\"ng-doc-list-control-row\"\n [id]=\"rowId($index)\"\n [attr.data-ng-doc-list-row]=\"row.kind\"\n [attr.data-ng-doc-highlighted]=\"highlighted() === $index\"\n [attr.aria-selected]=\"freeText ? highlighted() === $index : row.selected\"\n (pointerenter)=\"highlighted.set($index)\"\n (click)=\"pick(row)\">\n <span class=\"ng-doc-list-control-row-text\">\n {{ row.kind === 'add' ? texts.addValue({ value: row.label }) : row.label || '\"\"' }}\n </span>\n @if (row.selected) {\n <ng-doc-icon\n class=\"ng-doc-list-control-check\"\n icon=\"check\"\n aria-hidden=\"true\"></ng-doc-icon>\n }\n </div>\n }\n </div>\n } @else {\n <div class=\"ng-doc-list-control-no-matches\" role=\"status\">{{ texts.noMatches }}</div>\n }\n </div>\n</ng-template>\n", styles: [":host{display:flex;flex-direction:column;gap:var(--ng-doc-space-1);width:100%;min-width:0}.ng-doc-list-control-box{display:flex;align-items:flex-start;box-sizing:border-box;width:100%;min-width:0;min-height:var(--ng-doc-input-height);padding:5px var(--ng-doc-space-1) 5px 5px;border:var(--ng-doc-input-border);border-radius:var(--ng-doc-floated-border-radius);background-color:var(--ng-doc-input-background-color);cursor:text}.ng-doc-list-control-box:hover:not([data-ng-doc-disabled=true]){border:var(--ng-doc-input-border-hover)}.ng-doc-list-control-box:has(:focus-visible){box-shadow:var(--ng-doc-focus-ring)}.ng-doc-list-control-box[data-ng-doc-invalid=true],.ng-doc-list-control-box[data-ng-doc-invalid=true]:hover{border-color:var(--ng-doc-alert)}.ng-doc-list-control-box[data-ng-doc-disabled=true]{cursor:default;opacity:.6}.ng-doc-list-control-flow{display:flex;flex:1 1 auto;flex-wrap:wrap;align-items:center;gap:4px;min-width:0}.ng-doc-list-control-chips{display:contents;margin:0;padding:0;list-style:none}.ng-doc-list-control-item{display:flex;min-width:0;max-width:100%}.ng-doc-list-control-item[data-ng-doc-dragging=true] .ng-doc-list-control-chip{opacity:.5}.ng-doc-list-control-chip{gap:2px;height:24px;max-width:100%;padding:0 2px 0 0;overflow:hidden;--ng-doc-font-size: 13px;--ng-doc-line-height: 24px}.ng-doc-list-control-chip-text{min-width:0;overflow:hidden;padding-left:var(--ng-doc-space-2);border-radius:var(--ng-doc-radius-sm);text-overflow:ellipsis;outline:none;cursor:grab;-webkit-user-select:none;user-select:none;touch-action:none}.ng-doc-list-control-chip-text:focus-visible{box-shadow:var(--ng-doc-focus-ring)}[data-ng-doc-disabled=true] .ng-doc-list-control-chip-text{cursor:default}.ng-doc-list-control-empty-string{font-family:var(--ng-doc-code-font);opacity:.7}.ng-doc-list-control-remove{display:inline-flex;flex:none;align-items:center;justify-content:center;width:18px;height:18px;margin:0;padding:0;border:0;border-radius:var(--ng-doc-radius-sm);background:transparent;color:inherit;cursor:pointer;opacity:.6;--ng-doc-icon-width: 12px;--ng-doc-icon-height: 12px}.ng-doc-list-control-remove ::ng-deep svg{width:12px;height:12px}.ng-doc-list-control-remove:hover:not(:disabled){opacity:1;background:var(--ng-doc-hover-background)}.ng-doc-list-control-remove:disabled{cursor:default}.ng-doc-list-control-edit{box-sizing:content-box;height:22px;min-width:2ch;max-width:100%;margin:0;padding:0 var(--ng-doc-space-2);border:1px solid var(--ng-doc-primary);border-radius:var(--ng-doc-radius-sm);outline:none;background:var(--ng-doc-input-background-color);color:var(--ng-doc-text);font:inherit;font-size:13px}.ng-doc-list-control-edit[aria-invalid=true]{border-color:var(--ng-doc-alert)}.ng-doc-list-control-literal{padding:0 var(--ng-doc-space-1);font-family:var(--ng-doc-code-font);font-size:13px;line-height:24px;color:var(--ng-doc-text-muted)}.ng-doc-list-control-field{flex:1 1 32px;min-width:32px;height:24px;margin:0;padding:0 var(--ng-doc-space-1);border:0;outline:none;background:transparent;color:var(--ng-doc-text);font-family:var(--ng-doc-font-family);font-size:14px}.ng-doc-list-control-field::placeholder{color:var(--ng-doc-text-muted)}.ng-doc-list-control-reset{flex:none}.ng-doc-list-control-dropdown{display:contents}.ng-doc-list-control-error{font-size:12px;line-height:16px;color:var(--ng-doc-alert)}.ng-doc-list-control-status{position:absolute;width:1px;height:1px;overflow:hidden;clip-path:inset(50%);white-space:nowrap}.ng-doc-list-control-panel{box-sizing:border-box;min-width:160px;max-width:100vw;padding:var(--ng-doc-space-1) 0;font-family:var(--ng-doc-font-family);font-size:14px;line-height:20px}.ng-doc-list-control-row{display:flex;align-items:center;gap:var(--ng-doc-space-2);padding:4px calc(var(--ng-doc-base-gutter) * 1.5);color:var(--ng-doc-text);cursor:pointer}.ng-doc-list-control-row[data-ng-doc-highlighted=true]{background:var(--ng-doc-hover-background)}.ng-doc-list-control-row[data-ng-doc-list-row=add]{color:var(--ng-doc-heading-color);font-weight:500}.ng-doc-list-control-row[aria-selected=true]:not([data-ng-doc-list-row=add]){color:var(--ng-doc-heading-color)}.ng-doc-list-control-row-text{flex:1 1 auto;min-width:0;overflow:hidden;white-space:nowrap;text-overflow:ellipsis}.ng-doc-list-control-check{flex:none;--ng-doc-icon-color: var(--ng-doc-primary)}.ng-doc-list-control-no-matches{padding:4px calc(var(--ng-doc-base-gutter) * 1.5);color:var(--ng-doc-text-muted)}\n"] }]
|
|
697
|
+
}], ctorParameters: () => [] });
|
|
698
|
+
/**
|
|
699
|
+
* The playground control of arrays of a union of literals, such as `NgDocSize[]` or
|
|
700
|
+
* `('a' | 'b')[]`: a chip combobox (`NgDocListControl`) whose dropdown lists every value of the
|
|
701
|
+
* union, with the same texts as the control of the union (`NgDocTypeAliasControlComponent`).
|
|
702
|
+
* Picking a value adds it, and picking a value that the array holds, which is marked, removes it.
|
|
703
|
+
* The typed text filters the values; it is never an item itself.
|
|
704
|
+
*/
|
|
705
|
+
class NgDocTypeAliasListControlComponent extends NgDocListControl {
|
|
706
|
+
constructor() {
|
|
707
|
+
super();
|
|
708
|
+
/**
|
|
709
|
+
* The values of the items: the members of the union as written in code, plain values when
|
|
710
|
+
* `isManual` is set, or the names and values of enum members.
|
|
711
|
+
*/
|
|
712
|
+
this.options = input(undefined, /* @ts-ignore */
|
|
713
|
+
...(ngDevMode ? [{ debugName: "options" }] : /* istanbul ignore next */ []));
|
|
714
|
+
/** Whether `options` are values the user listed in the playground config. */
|
|
715
|
+
this.isManual = input(undefined, /* @ts-ignore */
|
|
716
|
+
...(ngDevMode ? [{ debugName: "isManual" }] : /* istanbul ignore next */ []));
|
|
717
|
+
this.freeText = false;
|
|
718
|
+
/** The values of the union that an item can be; `undefined`, `null` and repeats are left out. */
|
|
719
|
+
this.values = computed(() => {
|
|
720
|
+
const isManual = this.isManual();
|
|
721
|
+
const values = [];
|
|
722
|
+
for (const option of this.options() ?? []) {
|
|
723
|
+
try {
|
|
724
|
+
const value = resolvePlaygroundOption(option, isManual);
|
|
725
|
+
if (isListItem(value.value) &&
|
|
726
|
+
!values.some((other) => Object.is(other.value, value.value))) {
|
|
727
|
+
values.push(value);
|
|
728
|
+
}
|
|
729
|
+
}
|
|
730
|
+
catch {
|
|
731
|
+
// A member that is not a value, such as a type, is not offered.
|
|
732
|
+
}
|
|
733
|
+
}
|
|
734
|
+
return values;
|
|
735
|
+
}, /* @ts-ignore */
|
|
736
|
+
...(ngDevMode ? [{ debugName: "values" }] : /* istanbul ignore next */ []));
|
|
737
|
+
}
|
|
738
|
+
candidates() {
|
|
739
|
+
return this.values().map((option) => option.value);
|
|
740
|
+
}
|
|
741
|
+
labelOf(item) {
|
|
742
|
+
return (this.values().find((option) => Object.is(option.value, item))
|
|
743
|
+
?.label ?? String(item));
|
|
744
|
+
}
|
|
745
|
+
parseItem() {
|
|
746
|
+
return undefined;
|
|
747
|
+
}
|
|
748
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.2.1", ngImport: i0, type: NgDocTypeAliasListControlComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
749
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.2.1", type: NgDocTypeAliasListControlComponent, isStandalone: true, selector: "ng-doc-type-alias-list-control", inputs: { options: { classPropertyName: "options", publicName: "options", isSignal: true, isRequired: false, transformFunction: null }, isManual: { classPropertyName: "isManual", publicName: "isManual", isSignal: true, isRequired: false, transformFunction: null } }, usesInheritance: true, ngImport: i0, template: "<!-- The chips and the text field share one field-styled box, which the dropdown is attached to. -->\n<div\n #fieldBox\n class=\"ng-doc-list-control-box\"\n ngDocDropdownOrigin\n [attr.data-ng-doc-invalid]=\"invalid() || editInvalid()\"\n [attr.data-ng-doc-disabled]=\"disabled\"\n (click)=\"fieldClick($event, field)\">\n <div class=\"ng-doc-list-control-flow\">\n <!-- `display: contents` lets the chips and the text field wrap as one line of content. -->\n <ul class=\"ng-doc-list-control-chips\" role=\"list\">\n @for (item of shownItems(); track $index) {\n <li\n class=\"ng-doc-list-control-item\"\n [attr.data-ng-doc-list-item]=\"$index\"\n [attr.data-ng-doc-dragging]=\"dragIndex() === $index\">\n @if (editing() === $index) {\n <input\n class=\"ng-doc-list-control-edit\"\n data-ng-doc-list-edit\n spellcheck=\"false\"\n autocomplete=\"off\"\n [size]=\"editText().length || 1\"\n [value]=\"editText()\"\n [attr.aria-label]=\"texts.editItem({ item: chipText(item) })\"\n [attr.aria-invalid]=\"editInvalid() ? 'true' : null\"\n [attr.aria-describedby]=\"editInvalid() ? id + '-error' : null\"\n (input)=\"editText.set(edit.value)\"\n (keydown)=\"editKeydown($event)\"\n (blur)=\"editBlur()\"\n #edit />\n } @else {\n <ng-doc-tag class=\"ng-doc-list-control-chip\" mod=\"light\">\n <span\n class=\"ng-doc-list-control-chip-text\"\n data-ng-doc-list-chip\n [class.ng-doc-list-control-empty-string]=\"labelOf(item) === ''\"\n [attr.role]=\"freeText ? 'button' : null\"\n [attr.tabindex]=\"\n disabled ? null : $index === tabStop() || ($last && tabStop() > $index) ? 0 : -1\n \"\n [attr.aria-describedby]=\"\n freeText ? id + '-edit-keys ' + id + '-keys' : id + '-keys'\n \"\n (focus)=\"tabStop.set($index)\"\n (keydown)=\"chipKeydown($event, $index)\"\n (dblclick)=\"startEdit($index)\"\n (pointerdown)=\"chipPointerDown($event, $index)\"\n (pointermove)=\"chipPointerMove($event)\"\n (pointerup)=\"chipPointerUp($event)\"\n (pointercancel)=\"cancelDrag()\"\n >{{ chipText(item) }}</span\n >\n <button\n type=\"button\"\n class=\"ng-doc-list-control-remove\"\n data-ng-doc-list-remove\n tabindex=\"-1\"\n [attr.aria-label]=\"texts.removeItem({ item: chipText(item) })\"\n [disabled]=\"disabled\"\n (click)=\"remove($index)\">\n <ng-doc-icon icon=\"x\" aria-hidden=\"true\"></ng-doc-icon>\n </button>\n </ng-doc-tag>\n }\n </li>\n }\n </ul>\n @if (literal(); as literal) {\n <code class=\"ng-doc-list-control-literal\" data-ng-doc-list-literal>{{ literal }}</code>\n }\n <input\n #field\n class=\"ng-doc-list-control-field\"\n data-ng-doc-list-field\n role=\"combobox\"\n aria-autocomplete=\"list\"\n spellcheck=\"false\"\n autocomplete=\"off\"\n [value]=\"text()\"\n [placeholder]=\"items().length ? '' : texts.addItem\"\n [disabled]=\"disabled\"\n [attr.aria-label]=\"texts.addItem\"\n [attr.aria-expanded]=\"panelOpen()\"\n [attr.aria-controls]=\"id + '-rows'\"\n [attr.aria-activedescendant]=\"panelOpen() && highlighted() >= 0 ? rowId(highlighted()) : null\"\n [attr.aria-invalid]=\"invalid() ? 'true' : null\"\n [attr.aria-describedby]=\"invalid() ? id + '-error' : null\"\n (input)=\"type(field.value)\"\n (keydown)=\"fieldKeydown($event, field)\" />\n </div>\n @if (canReset()) {\n <button\n ng-doc-button-icon-transparent\n type=\"button\"\n size=\"small\"\n class=\"ng-doc-list-control-reset\"\n data-ng-doc-list-reset\n [rounded]=\"false\"\n [attr.aria-label]=\"texts.resetToDefault\"\n [disabled]=\"disabled\"\n (click)=\"reset()\">\n <ng-doc-icon icon=\"x\" aria-hidden=\"true\"></ng-doc-icon>\n </button>\n }\n <!-- The dropdown's host takes the focus while it is open, to move it into the overlay. The rows\n here are never focused (the text field names the highlighted one), so the host is inert. -->\n <span class=\"ng-doc-list-control-dropdown\" inert>\n <ng-doc-dropdown\n [content]=\"panel\"\n [positions]=\"['bottom-center', 'top-center']\"\n [maxHeight]=\"240\"\n (beforeClose)=\"expanded.set(false)\"></ng-doc-dropdown>\n </span>\n</div>\n@if (invalid() || editInvalid()) {\n <span class=\"ng-doc-list-control-error\" [id]=\"id + '-error'\">{{ texts.invalidNumber }}</span>\n}\n<span hidden [id]=\"id + '-keys'\">{{ texts.itemKeys }}</span>\n<span hidden [id]=\"id + '-edit-keys'\">{{ texts.editKeys }}</span>\n<span class=\"ng-doc-list-control-status\" role=\"status\">{{ announcement() }}</span>\n\n<ng-template #panel>\n <!-- A press on a row keeps the focus in the text field. -->\n <div\n class=\"ng-doc-list-control-panel\"\n [style.width.px]=\"panelWidth()\"\n (mousedown)=\"$event.preventDefault()\">\n @if (rows().length) {\n <div\n role=\"listbox\"\n class=\"ng-doc-list-control-rows\"\n [id]=\"id + '-rows'\"\n [attr.aria-label]=\"texts.suggestions\"\n [attr.aria-multiselectable]=\"freeText ? null : 'true'\">\n @for (row of rows(); track $index) {\n <div\n role=\"option\"\n class=\"ng-doc-list-control-row\"\n [id]=\"rowId($index)\"\n [attr.data-ng-doc-list-row]=\"row.kind\"\n [attr.data-ng-doc-highlighted]=\"highlighted() === $index\"\n [attr.aria-selected]=\"freeText ? highlighted() === $index : row.selected\"\n (pointerenter)=\"highlighted.set($index)\"\n (click)=\"pick(row)\">\n <span class=\"ng-doc-list-control-row-text\">\n {{ row.kind === 'add' ? texts.addValue({ value: row.label }) : row.label || '\"\"' }}\n </span>\n @if (row.selected) {\n <ng-doc-icon\n class=\"ng-doc-list-control-check\"\n icon=\"check\"\n aria-hidden=\"true\"></ng-doc-icon>\n }\n </div>\n }\n </div>\n } @else {\n <div class=\"ng-doc-list-control-no-matches\" role=\"status\">{{ texts.noMatches }}</div>\n }\n </div>\n</ng-template>\n", styles: [":host{display:flex;flex-direction:column;gap:var(--ng-doc-space-1);width:100%;min-width:0}.ng-doc-list-control-box{display:flex;align-items:flex-start;box-sizing:border-box;width:100%;min-width:0;min-height:var(--ng-doc-input-height);padding:5px var(--ng-doc-space-1) 5px 5px;border:var(--ng-doc-input-border);border-radius:var(--ng-doc-floated-border-radius);background-color:var(--ng-doc-input-background-color);cursor:text}.ng-doc-list-control-box:hover:not([data-ng-doc-disabled=true]){border:var(--ng-doc-input-border-hover)}.ng-doc-list-control-box:has(:focus-visible){box-shadow:var(--ng-doc-focus-ring)}.ng-doc-list-control-box[data-ng-doc-invalid=true],.ng-doc-list-control-box[data-ng-doc-invalid=true]:hover{border-color:var(--ng-doc-alert)}.ng-doc-list-control-box[data-ng-doc-disabled=true]{cursor:default;opacity:.6}.ng-doc-list-control-flow{display:flex;flex:1 1 auto;flex-wrap:wrap;align-items:center;gap:4px;min-width:0}.ng-doc-list-control-chips{display:contents;margin:0;padding:0;list-style:none}.ng-doc-list-control-item{display:flex;min-width:0;max-width:100%}.ng-doc-list-control-item[data-ng-doc-dragging=true] .ng-doc-list-control-chip{opacity:.5}.ng-doc-list-control-chip{gap:2px;height:24px;max-width:100%;padding:0 2px 0 0;overflow:hidden;--ng-doc-font-size: 13px;--ng-doc-line-height: 24px}.ng-doc-list-control-chip-text{min-width:0;overflow:hidden;padding-left:var(--ng-doc-space-2);border-radius:var(--ng-doc-radius-sm);text-overflow:ellipsis;outline:none;cursor:grab;-webkit-user-select:none;user-select:none;touch-action:none}.ng-doc-list-control-chip-text:focus-visible{box-shadow:var(--ng-doc-focus-ring)}[data-ng-doc-disabled=true] .ng-doc-list-control-chip-text{cursor:default}.ng-doc-list-control-empty-string{font-family:var(--ng-doc-code-font);opacity:.7}.ng-doc-list-control-remove{display:inline-flex;flex:none;align-items:center;justify-content:center;width:18px;height:18px;margin:0;padding:0;border:0;border-radius:var(--ng-doc-radius-sm);background:transparent;color:inherit;cursor:pointer;opacity:.6;--ng-doc-icon-width: 12px;--ng-doc-icon-height: 12px}.ng-doc-list-control-remove ::ng-deep svg{width:12px;height:12px}.ng-doc-list-control-remove:hover:not(:disabled){opacity:1;background:var(--ng-doc-hover-background)}.ng-doc-list-control-remove:disabled{cursor:default}.ng-doc-list-control-edit{box-sizing:content-box;height:22px;min-width:2ch;max-width:100%;margin:0;padding:0 var(--ng-doc-space-2);border:1px solid var(--ng-doc-primary);border-radius:var(--ng-doc-radius-sm);outline:none;background:var(--ng-doc-input-background-color);color:var(--ng-doc-text);font:inherit;font-size:13px}.ng-doc-list-control-edit[aria-invalid=true]{border-color:var(--ng-doc-alert)}.ng-doc-list-control-literal{padding:0 var(--ng-doc-space-1);font-family:var(--ng-doc-code-font);font-size:13px;line-height:24px;color:var(--ng-doc-text-muted)}.ng-doc-list-control-field{flex:1 1 32px;min-width:32px;height:24px;margin:0;padding:0 var(--ng-doc-space-1);border:0;outline:none;background:transparent;color:var(--ng-doc-text);font-family:var(--ng-doc-font-family);font-size:14px}.ng-doc-list-control-field::placeholder{color:var(--ng-doc-text-muted)}.ng-doc-list-control-reset{flex:none}.ng-doc-list-control-dropdown{display:contents}.ng-doc-list-control-error{font-size:12px;line-height:16px;color:var(--ng-doc-alert)}.ng-doc-list-control-status{position:absolute;width:1px;height:1px;overflow:hidden;clip-path:inset(50%);white-space:nowrap}.ng-doc-list-control-panel{box-sizing:border-box;min-width:160px;max-width:100vw;padding:var(--ng-doc-space-1) 0;font-family:var(--ng-doc-font-family);font-size:14px;line-height:20px}.ng-doc-list-control-row{display:flex;align-items:center;gap:var(--ng-doc-space-2);padding:4px calc(var(--ng-doc-base-gutter) * 1.5);color:var(--ng-doc-text);cursor:pointer}.ng-doc-list-control-row[data-ng-doc-highlighted=true]{background:var(--ng-doc-hover-background)}.ng-doc-list-control-row[data-ng-doc-list-row=add]{color:var(--ng-doc-heading-color);font-weight:500}.ng-doc-list-control-row[aria-selected=true]:not([data-ng-doc-list-row=add]){color:var(--ng-doc-heading-color)}.ng-doc-list-control-row-text{flex:1 1 auto;min-width:0;overflow:hidden;white-space:nowrap;text-overflow:ellipsis}.ng-doc-list-control-check{flex:none;--ng-doc-icon-color: var(--ng-doc-primary)}.ng-doc-list-control-no-matches{padding:4px calc(var(--ng-doc-base-gutter) * 1.5);color:var(--ng-doc-text-muted)}\n"], dependencies: [{ kind: "component", type: NgDocButtonIconComponent, selector: "button[ng-doc-button-icon], a[ng-doc-button-icon], button[ng-doc-button-icon-raised], a[ng-doc-button-icon-raised], button[ng-doc-button-icon-transparent], a[ng-doc-button-icon-transparent]", inputs: ["size", "rounded"] }, { kind: "component", type: NgDocDropdownComponent, selector: "ng-doc-dropdown", inputs: ["content", "origin", "closeIfOutsideClick", "closeIfInnerClick", "withArrow", "borderOffset", "panelClass", "contactBorder", "hasBackdrop", "positions", "minHeight", "maxHeight", "height", "minWidth", "maxWidth", "width"], outputs: ["beforeOpen", "afterOpen", "beforeClose", "afterClose"] }, { kind: "directive", type: NgDocDropdownOriginDirective, selector: "[ngDocDropdownOrigin]", exportAs: ["ngDocDropdownOrigin"] }, { kind: "component", type: NgDocIconComponent, selector: "ng-doc-icon", inputs: ["icon", "customIcon", "size"] }, { kind: "component", type: NgDocTagComponent, selector: "ng-doc-tag", inputs: ["color", "size", "mod"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
750
|
+
}
|
|
751
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.2.1", ngImport: i0, type: NgDocTypeAliasListControlComponent, decorators: [{
|
|
752
|
+
type: Component,
|
|
753
|
+
args: [{ selector: 'ng-doc-type-alias-list-control', changeDetection: ChangeDetectionStrategy.OnPush, imports: LIST_CONTROL_IMPORTS, template: "<!-- The chips and the text field share one field-styled box, which the dropdown is attached to. -->\n<div\n #fieldBox\n class=\"ng-doc-list-control-box\"\n ngDocDropdownOrigin\n [attr.data-ng-doc-invalid]=\"invalid() || editInvalid()\"\n [attr.data-ng-doc-disabled]=\"disabled\"\n (click)=\"fieldClick($event, field)\">\n <div class=\"ng-doc-list-control-flow\">\n <!-- `display: contents` lets the chips and the text field wrap as one line of content. -->\n <ul class=\"ng-doc-list-control-chips\" role=\"list\">\n @for (item of shownItems(); track $index) {\n <li\n class=\"ng-doc-list-control-item\"\n [attr.data-ng-doc-list-item]=\"$index\"\n [attr.data-ng-doc-dragging]=\"dragIndex() === $index\">\n @if (editing() === $index) {\n <input\n class=\"ng-doc-list-control-edit\"\n data-ng-doc-list-edit\n spellcheck=\"false\"\n autocomplete=\"off\"\n [size]=\"editText().length || 1\"\n [value]=\"editText()\"\n [attr.aria-label]=\"texts.editItem({ item: chipText(item) })\"\n [attr.aria-invalid]=\"editInvalid() ? 'true' : null\"\n [attr.aria-describedby]=\"editInvalid() ? id + '-error' : null\"\n (input)=\"editText.set(edit.value)\"\n (keydown)=\"editKeydown($event)\"\n (blur)=\"editBlur()\"\n #edit />\n } @else {\n <ng-doc-tag class=\"ng-doc-list-control-chip\" mod=\"light\">\n <span\n class=\"ng-doc-list-control-chip-text\"\n data-ng-doc-list-chip\n [class.ng-doc-list-control-empty-string]=\"labelOf(item) === ''\"\n [attr.role]=\"freeText ? 'button' : null\"\n [attr.tabindex]=\"\n disabled ? null : $index === tabStop() || ($last && tabStop() > $index) ? 0 : -1\n \"\n [attr.aria-describedby]=\"\n freeText ? id + '-edit-keys ' + id + '-keys' : id + '-keys'\n \"\n (focus)=\"tabStop.set($index)\"\n (keydown)=\"chipKeydown($event, $index)\"\n (dblclick)=\"startEdit($index)\"\n (pointerdown)=\"chipPointerDown($event, $index)\"\n (pointermove)=\"chipPointerMove($event)\"\n (pointerup)=\"chipPointerUp($event)\"\n (pointercancel)=\"cancelDrag()\"\n >{{ chipText(item) }}</span\n >\n <button\n type=\"button\"\n class=\"ng-doc-list-control-remove\"\n data-ng-doc-list-remove\n tabindex=\"-1\"\n [attr.aria-label]=\"texts.removeItem({ item: chipText(item) })\"\n [disabled]=\"disabled\"\n (click)=\"remove($index)\">\n <ng-doc-icon icon=\"x\" aria-hidden=\"true\"></ng-doc-icon>\n </button>\n </ng-doc-tag>\n }\n </li>\n }\n </ul>\n @if (literal(); as literal) {\n <code class=\"ng-doc-list-control-literal\" data-ng-doc-list-literal>{{ literal }}</code>\n }\n <input\n #field\n class=\"ng-doc-list-control-field\"\n data-ng-doc-list-field\n role=\"combobox\"\n aria-autocomplete=\"list\"\n spellcheck=\"false\"\n autocomplete=\"off\"\n [value]=\"text()\"\n [placeholder]=\"items().length ? '' : texts.addItem\"\n [disabled]=\"disabled\"\n [attr.aria-label]=\"texts.addItem\"\n [attr.aria-expanded]=\"panelOpen()\"\n [attr.aria-controls]=\"id + '-rows'\"\n [attr.aria-activedescendant]=\"panelOpen() && highlighted() >= 0 ? rowId(highlighted()) : null\"\n [attr.aria-invalid]=\"invalid() ? 'true' : null\"\n [attr.aria-describedby]=\"invalid() ? id + '-error' : null\"\n (input)=\"type(field.value)\"\n (keydown)=\"fieldKeydown($event, field)\" />\n </div>\n @if (canReset()) {\n <button\n ng-doc-button-icon-transparent\n type=\"button\"\n size=\"small\"\n class=\"ng-doc-list-control-reset\"\n data-ng-doc-list-reset\n [rounded]=\"false\"\n [attr.aria-label]=\"texts.resetToDefault\"\n [disabled]=\"disabled\"\n (click)=\"reset()\">\n <ng-doc-icon icon=\"x\" aria-hidden=\"true\"></ng-doc-icon>\n </button>\n }\n <!-- The dropdown's host takes the focus while it is open, to move it into the overlay. The rows\n here are never focused (the text field names the highlighted one), so the host is inert. -->\n <span class=\"ng-doc-list-control-dropdown\" inert>\n <ng-doc-dropdown\n [content]=\"panel\"\n [positions]=\"['bottom-center', 'top-center']\"\n [maxHeight]=\"240\"\n (beforeClose)=\"expanded.set(false)\"></ng-doc-dropdown>\n </span>\n</div>\n@if (invalid() || editInvalid()) {\n <span class=\"ng-doc-list-control-error\" [id]=\"id + '-error'\">{{ texts.invalidNumber }}</span>\n}\n<span hidden [id]=\"id + '-keys'\">{{ texts.itemKeys }}</span>\n<span hidden [id]=\"id + '-edit-keys'\">{{ texts.editKeys }}</span>\n<span class=\"ng-doc-list-control-status\" role=\"status\">{{ announcement() }}</span>\n\n<ng-template #panel>\n <!-- A press on a row keeps the focus in the text field. -->\n <div\n class=\"ng-doc-list-control-panel\"\n [style.width.px]=\"panelWidth()\"\n (mousedown)=\"$event.preventDefault()\">\n @if (rows().length) {\n <div\n role=\"listbox\"\n class=\"ng-doc-list-control-rows\"\n [id]=\"id + '-rows'\"\n [attr.aria-label]=\"texts.suggestions\"\n [attr.aria-multiselectable]=\"freeText ? null : 'true'\">\n @for (row of rows(); track $index) {\n <div\n role=\"option\"\n class=\"ng-doc-list-control-row\"\n [id]=\"rowId($index)\"\n [attr.data-ng-doc-list-row]=\"row.kind\"\n [attr.data-ng-doc-highlighted]=\"highlighted() === $index\"\n [attr.aria-selected]=\"freeText ? highlighted() === $index : row.selected\"\n (pointerenter)=\"highlighted.set($index)\"\n (click)=\"pick(row)\">\n <span class=\"ng-doc-list-control-row-text\">\n {{ row.kind === 'add' ? texts.addValue({ value: row.label }) : row.label || '\"\"' }}\n </span>\n @if (row.selected) {\n <ng-doc-icon\n class=\"ng-doc-list-control-check\"\n icon=\"check\"\n aria-hidden=\"true\"></ng-doc-icon>\n }\n </div>\n }\n </div>\n } @else {\n <div class=\"ng-doc-list-control-no-matches\" role=\"status\">{{ texts.noMatches }}</div>\n }\n </div>\n</ng-template>\n", styles: [":host{display:flex;flex-direction:column;gap:var(--ng-doc-space-1);width:100%;min-width:0}.ng-doc-list-control-box{display:flex;align-items:flex-start;box-sizing:border-box;width:100%;min-width:0;min-height:var(--ng-doc-input-height);padding:5px var(--ng-doc-space-1) 5px 5px;border:var(--ng-doc-input-border);border-radius:var(--ng-doc-floated-border-radius);background-color:var(--ng-doc-input-background-color);cursor:text}.ng-doc-list-control-box:hover:not([data-ng-doc-disabled=true]){border:var(--ng-doc-input-border-hover)}.ng-doc-list-control-box:has(:focus-visible){box-shadow:var(--ng-doc-focus-ring)}.ng-doc-list-control-box[data-ng-doc-invalid=true],.ng-doc-list-control-box[data-ng-doc-invalid=true]:hover{border-color:var(--ng-doc-alert)}.ng-doc-list-control-box[data-ng-doc-disabled=true]{cursor:default;opacity:.6}.ng-doc-list-control-flow{display:flex;flex:1 1 auto;flex-wrap:wrap;align-items:center;gap:4px;min-width:0}.ng-doc-list-control-chips{display:contents;margin:0;padding:0;list-style:none}.ng-doc-list-control-item{display:flex;min-width:0;max-width:100%}.ng-doc-list-control-item[data-ng-doc-dragging=true] .ng-doc-list-control-chip{opacity:.5}.ng-doc-list-control-chip{gap:2px;height:24px;max-width:100%;padding:0 2px 0 0;overflow:hidden;--ng-doc-font-size: 13px;--ng-doc-line-height: 24px}.ng-doc-list-control-chip-text{min-width:0;overflow:hidden;padding-left:var(--ng-doc-space-2);border-radius:var(--ng-doc-radius-sm);text-overflow:ellipsis;outline:none;cursor:grab;-webkit-user-select:none;user-select:none;touch-action:none}.ng-doc-list-control-chip-text:focus-visible{box-shadow:var(--ng-doc-focus-ring)}[data-ng-doc-disabled=true] .ng-doc-list-control-chip-text{cursor:default}.ng-doc-list-control-empty-string{font-family:var(--ng-doc-code-font);opacity:.7}.ng-doc-list-control-remove{display:inline-flex;flex:none;align-items:center;justify-content:center;width:18px;height:18px;margin:0;padding:0;border:0;border-radius:var(--ng-doc-radius-sm);background:transparent;color:inherit;cursor:pointer;opacity:.6;--ng-doc-icon-width: 12px;--ng-doc-icon-height: 12px}.ng-doc-list-control-remove ::ng-deep svg{width:12px;height:12px}.ng-doc-list-control-remove:hover:not(:disabled){opacity:1;background:var(--ng-doc-hover-background)}.ng-doc-list-control-remove:disabled{cursor:default}.ng-doc-list-control-edit{box-sizing:content-box;height:22px;min-width:2ch;max-width:100%;margin:0;padding:0 var(--ng-doc-space-2);border:1px solid var(--ng-doc-primary);border-radius:var(--ng-doc-radius-sm);outline:none;background:var(--ng-doc-input-background-color);color:var(--ng-doc-text);font:inherit;font-size:13px}.ng-doc-list-control-edit[aria-invalid=true]{border-color:var(--ng-doc-alert)}.ng-doc-list-control-literal{padding:0 var(--ng-doc-space-1);font-family:var(--ng-doc-code-font);font-size:13px;line-height:24px;color:var(--ng-doc-text-muted)}.ng-doc-list-control-field{flex:1 1 32px;min-width:32px;height:24px;margin:0;padding:0 var(--ng-doc-space-1);border:0;outline:none;background:transparent;color:var(--ng-doc-text);font-family:var(--ng-doc-font-family);font-size:14px}.ng-doc-list-control-field::placeholder{color:var(--ng-doc-text-muted)}.ng-doc-list-control-reset{flex:none}.ng-doc-list-control-dropdown{display:contents}.ng-doc-list-control-error{font-size:12px;line-height:16px;color:var(--ng-doc-alert)}.ng-doc-list-control-status{position:absolute;width:1px;height:1px;overflow:hidden;clip-path:inset(50%);white-space:nowrap}.ng-doc-list-control-panel{box-sizing:border-box;min-width:160px;max-width:100vw;padding:var(--ng-doc-space-1) 0;font-family:var(--ng-doc-font-family);font-size:14px;line-height:20px}.ng-doc-list-control-row{display:flex;align-items:center;gap:var(--ng-doc-space-2);padding:4px calc(var(--ng-doc-base-gutter) * 1.5);color:var(--ng-doc-text);cursor:pointer}.ng-doc-list-control-row[data-ng-doc-highlighted=true]{background:var(--ng-doc-hover-background)}.ng-doc-list-control-row[data-ng-doc-list-row=add]{color:var(--ng-doc-heading-color);font-weight:500}.ng-doc-list-control-row[aria-selected=true]:not([data-ng-doc-list-row=add]){color:var(--ng-doc-heading-color)}.ng-doc-list-control-row-text{flex:1 1 auto;min-width:0;overflow:hidden;white-space:nowrap;text-overflow:ellipsis}.ng-doc-list-control-check{flex:none;--ng-doc-icon-color: var(--ng-doc-primary)}.ng-doc-list-control-no-matches{padding:4px calc(var(--ng-doc-base-gutter) * 1.5);color:var(--ng-doc-text-muted)}\n"] }]
|
|
754
|
+
}], ctorParameters: () => [], propDecorators: { options: [{ type: i0.Input, args: [{ isSignal: true, alias: "options", required: false }] }], isManual: [{ type: i0.Input, args: [{ isSignal: true, alias: "isManual", required: false }] }] } });
|
|
755
|
+
/**
|
|
756
|
+
* Whether a value can be an item of an array control.
|
|
757
|
+
* @param value - The value.
|
|
758
|
+
*/
|
|
759
|
+
function isListItem(value) {
|
|
760
|
+
return typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean';
|
|
761
|
+
}
|
|
762
|
+
/**
|
|
763
|
+
* The items with one of them moved to another position.
|
|
764
|
+
* @param items - The items.
|
|
765
|
+
* @param from - The position of the item.
|
|
766
|
+
* @param to - Its new position.
|
|
767
|
+
*/
|
|
768
|
+
function moveItem(items, from, to) {
|
|
769
|
+
const order = [...items];
|
|
770
|
+
const [item] = order.splice(from, 1);
|
|
771
|
+
order.splice(to, 0, item);
|
|
772
|
+
return order;
|
|
773
|
+
}
|
|
774
|
+
/**
|
|
775
|
+
* Whether two values of a list control hold the same items in the same order. `undefined` and
|
|
776
|
+
* `null` are values of their own.
|
|
777
|
+
* @param a - The first value.
|
|
778
|
+
* @param b - The second value.
|
|
779
|
+
*/
|
|
780
|
+
function sameItems(a, b) {
|
|
781
|
+
if (Array.isArray(a) && Array.isArray(b)) {
|
|
782
|
+
return (a.length === b.length && a.every((item, index) => Object.is(item, b[index])));
|
|
783
|
+
}
|
|
784
|
+
return a === b;
|
|
785
|
+
}
|
|
786
|
+
|
|
787
|
+
/**
|
|
788
|
+
* Generated bundle index. Do not edit.
|
|
789
|
+
*/
|
|
790
|
+
|
|
791
|
+
export { NgDocListControl, NgDocNumberListControlComponent, NgDocStringListControlComponent, NgDocTypeAliasListControlComponent };
|
|
792
|
+
//# sourceMappingURL=ng-doc-app-type-controls-list-control.mjs.map
|