ngwr 15.4.0 → 15.5.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/comment/styles/_index.scss +270 -0
- package/dialog/styles/_index.scss +6 -0
- package/fesm2022/ngwr-comment-testing.mjs +60 -0
- package/fesm2022/ngwr-comment-testing.mjs.map +1 -0
- package/fesm2022/ngwr-comment.mjs +516 -0
- package/fesm2022/ngwr-comment.mjs.map +1 -0
- package/fesm2022/ngwr-dialog-testing.mjs +14 -0
- package/fesm2022/ngwr-dialog-testing.mjs.map +1 -1
- package/fesm2022/ngwr-dialog.mjs +293 -134
- package/fesm2022/ngwr-dialog.mjs.map +1 -1
- package/fesm2022/ngwr-drawer.mjs +2 -0
- package/fesm2022/ngwr-drawer.mjs.map +1 -1
- package/fesm2022/ngwr-editor.mjs +1 -1
- package/fesm2022/ngwr-hotkey.mjs +86 -6
- package/fesm2022/ngwr-hotkey.mjs.map +1 -1
- package/fesm2022/ngwr-i18n-ar.mjs +19 -0
- package/fesm2022/ngwr-i18n-ar.mjs.map +1 -1
- package/fesm2022/ngwr-i18n-cs.mjs +19 -0
- package/fesm2022/ngwr-i18n-cs.mjs.map +1 -1
- package/fesm2022/ngwr-i18n-de.mjs +19 -0
- package/fesm2022/ngwr-i18n-de.mjs.map +1 -1
- package/fesm2022/ngwr-i18n-en.mjs +19 -0
- package/fesm2022/ngwr-i18n-en.mjs.map +1 -1
- package/fesm2022/ngwr-i18n-es.mjs +19 -0
- package/fesm2022/ngwr-i18n-es.mjs.map +1 -1
- package/fesm2022/ngwr-i18n-fr.mjs +19 -0
- package/fesm2022/ngwr-i18n-fr.mjs.map +1 -1
- package/fesm2022/ngwr-i18n-he.mjs +19 -0
- package/fesm2022/ngwr-i18n-he.mjs.map +1 -1
- package/fesm2022/ngwr-i18n-hi.mjs +19 -0
- package/fesm2022/ngwr-i18n-hi.mjs.map +1 -1
- package/fesm2022/ngwr-i18n-id.mjs +19 -0
- package/fesm2022/ngwr-i18n-id.mjs.map +1 -1
- package/fesm2022/ngwr-i18n-it.mjs +19 -0
- package/fesm2022/ngwr-i18n-it.mjs.map +1 -1
- package/fesm2022/ngwr-i18n-ja.mjs +19 -0
- package/fesm2022/ngwr-i18n-ja.mjs.map +1 -1
- package/fesm2022/ngwr-i18n-ko.mjs +19 -0
- package/fesm2022/ngwr-i18n-ko.mjs.map +1 -1
- package/fesm2022/ngwr-i18n-nl.mjs +19 -0
- package/fesm2022/ngwr-i18n-nl.mjs.map +1 -1
- package/fesm2022/ngwr-i18n-pl.mjs +19 -0
- package/fesm2022/ngwr-i18n-pl.mjs.map +1 -1
- package/fesm2022/ngwr-i18n-pt.mjs +19 -0
- package/fesm2022/ngwr-i18n-pt.mjs.map +1 -1
- package/fesm2022/ngwr-i18n-ru.mjs +19 -0
- package/fesm2022/ngwr-i18n-ru.mjs.map +1 -1
- package/fesm2022/ngwr-i18n-sv.mjs +19 -0
- package/fesm2022/ngwr-i18n-sv.mjs.map +1 -1
- package/fesm2022/ngwr-i18n-tr.mjs +19 -0
- package/fesm2022/ngwr-i18n-tr.mjs.map +1 -1
- package/fesm2022/ngwr-i18n-uk.mjs +19 -0
- package/fesm2022/ngwr-i18n-uk.mjs.map +1 -1
- package/fesm2022/ngwr-i18n-vi.mjs +19 -0
- package/fesm2022/ngwr-i18n-vi.mjs.map +1 -1
- package/fesm2022/ngwr-i18n-zh-tw.mjs +19 -0
- package/fesm2022/ngwr-i18n-zh-tw.mjs.map +1 -1
- package/fesm2022/ngwr-i18n-zh.mjs +19 -0
- package/fesm2022/ngwr-i18n-zh.mjs.map +1 -1
- package/fesm2022/ngwr-input-number-testing.mjs.map +1 -1
- package/fesm2022/ngwr-input-number.mjs +109 -12
- package/fesm2022/ngwr-input-number.mjs.map +1 -1
- package/fesm2022/ngwr-keyboard.mjs +78 -18
- package/fesm2022/ngwr-keyboard.mjs.map +1 -1
- package/fesm2022/ngwr-platform.mjs +134 -2
- package/fesm2022/ngwr-platform.mjs.map +1 -1
- package/fesm2022/ngwr-schema-form.mjs +2 -1
- package/fesm2022/ngwr-schema-form.mjs.map +1 -1
- package/fesm2022/ngwr-select.mjs.map +1 -1
- package/fesm2022/ngwr-storage.mjs +54 -33
- package/fesm2022/ngwr-storage.mjs.map +1 -1
- package/fesm2022/ngwr-table.mjs +90 -34
- package/fesm2022/ngwr-table.mjs.map +1 -1
- package/fesm2022/ngwr-transfer.mjs +1 -1
- package/fesm2022/ngwr-utils-router.mjs +165 -0
- package/fesm2022/ngwr-utils-router.mjs.map +1 -0
- package/fesm2022/ngwr-utils.mjs +311 -3
- package/fesm2022/ngwr-utils.mjs.map +1 -1
- package/fesm2022/ngwr-version.mjs +1 -1
- package/fesm2022/ngwr-version.mjs.map +1 -1
- package/i18n/ar.json +19 -0
- package/i18n/cs.json +19 -0
- package/i18n/de.json +19 -0
- package/i18n/en.json +19 -0
- package/i18n/es.json +19 -0
- package/i18n/fr.json +19 -0
- package/i18n/he.json +19 -0
- package/i18n/hi.json +19 -0
- package/i18n/id.json +19 -0
- package/i18n/it.json +19 -0
- package/i18n/ja.json +19 -0
- package/i18n/ko.json +19 -0
- package/i18n/nl.json +19 -0
- package/i18n/pl.json +19 -0
- package/i18n/pt.json +19 -0
- package/i18n/ru.json +19 -0
- package/i18n/sv.json +19 -0
- package/i18n/tr.json +19 -0
- package/i18n/uk.json +19 -0
- package/i18n/vi.json +19 -0
- package/i18n/zh-TW.json +19 -0
- package/i18n/zh.json +19 -0
- package/llms-full.txt +30 -14
- package/package.json +14 -1
- package/schematics/use/symbol-map.json +2 -0
- package/skills/ngwr/SKILL.md +1 -1
- package/skills/ngwr/references/catalog.md +3 -0
- package/table/styles/_index.scss +17 -0
- package/types/ngwr-badge.d.ts +2 -2
- package/types/ngwr-button.d.ts +1 -1
- package/types/ngwr-comment-testing.d.ts +90 -0
- package/types/ngwr-comment.d.ts +304 -0
- package/types/ngwr-dialog-testing.d.ts +7 -0
- package/types/ngwr-dialog.d.ts +109 -6
- package/types/ngwr-divider.d.ts +1 -1
- package/types/ngwr-drawer.d.ts +1 -1
- package/types/ngwr-hotkey.d.ts +60 -3
- package/types/ngwr-image-cropper.d.ts +1 -1
- package/types/ngwr-input-number-testing.d.ts +5 -0
- package/types/ngwr-input-number.d.ts +91 -12
- package/types/ngwr-keyboard.d.ts +28 -1
- package/types/ngwr-platform.d.ts +73 -7
- package/types/ngwr-popconfirm.d.ts +2 -2
- package/types/ngwr-progress.d.ts +1 -1
- package/types/ngwr-select.d.ts +19 -13
- package/types/ngwr-skeleton.d.ts +1 -1
- package/types/ngwr-storage.d.ts +33 -3
- package/types/ngwr-table.d.ts +145 -44
- package/types/ngwr-utils-router.d.ts +95 -0
- package/types/ngwr-utils.d.ts +262 -2
- package/types/ngwr-version.d.ts +1 -1
|
@@ -0,0 +1,516 @@
|
|
|
1
|
+
import * as i0 from "@angular/core";
|
|
2
|
+
import { Component, ElementRef, ViewEncapsulation, computed, inject, input, model, output, viewChild } from "@angular/core";
|
|
3
|
+
import { coerceBooleanProperty, coerceNumberProperty } from "@angular/cdk/coercion";
|
|
4
|
+
import { useI18nText } from "ngwr/i18n";
|
|
5
|
+
import { KEYS, isComposing } from "ngwr/utils";
|
|
6
|
+
import { WrButton } from "ngwr/button";
|
|
7
|
+
import { WrTextarea } from "ngwr/textarea";
|
|
8
|
+
/**
|
|
9
|
+
* @license
|
|
10
|
+
*
|
|
11
|
+
* Use of this source code is governed by an MIT-style license that can be
|
|
12
|
+
* found in the LICENSE file at https://github.com/thekhegay/ngwr/blob/main/LICENSE
|
|
13
|
+
*/
|
|
14
|
+
var WrComment = class WrComment {
|
|
15
|
+
author = input("", ...ngDevMode ? [{ debugName: "author" }] : /* istanbul ignore next */ []);
|
|
16
|
+
time = input("", ...ngDevMode ? [{ debugName: "time" }] : /* istanbul ignore next */ []);
|
|
17
|
+
datetime = input(null, ...ngDevMode ? [{ debugName: "datetime" }] : /* istanbul ignore next */ []);
|
|
18
|
+
variant = input("row", ...ngDevMode ? [{ debugName: "variant" }] : /* istanbul ignore next */ []);
|
|
19
|
+
side = input("start", ...ngDevMode ? [{ debugName: "side" }] : /* istanbul ignore next */ []);
|
|
20
|
+
repliesLabel = input(null, ...ngDevMode ? [{ debugName: "repliesLabel" }] : /* istanbul ignore next */ []);
|
|
21
|
+
resolvedRepliesLabel = useI18nText(this.repliesLabel, "comment.replies", "Replies");
|
|
22
|
+
isoDatetime = computed(() => {
|
|
23
|
+
const value = this.datetime();
|
|
24
|
+
if (value === null) return null;
|
|
25
|
+
if (value instanceof Date) return Number.isNaN(value.getTime()) ? null : value.toISOString();
|
|
26
|
+
return value || null;
|
|
27
|
+
}, ...ngDevMode ? [{ debugName: "isoDatetime" }] : /* istanbul ignore next */ []);
|
|
28
|
+
classes = computed(() => {
|
|
29
|
+
const parts = ["wr-comment"];
|
|
30
|
+
if (this.variant() === "bubble") parts.push("wr-comment--bubble", `wr-comment--${this.side()}`);
|
|
31
|
+
return parts.join(" ");
|
|
32
|
+
}, ...ngDevMode ? [{ debugName: "classes" }] : /* istanbul ignore next */ []);
|
|
33
|
+
static ɵfac = i0.ɵɵngDeclareFactory({
|
|
34
|
+
minVersion: "12.0.0",
|
|
35
|
+
version: "22.2.4",
|
|
36
|
+
ngImport: i0,
|
|
37
|
+
type: WrComment,
|
|
38
|
+
deps: [],
|
|
39
|
+
target: i0.ɵɵFactoryTarget.Component
|
|
40
|
+
});
|
|
41
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({
|
|
42
|
+
minVersion: "17.0.0",
|
|
43
|
+
version: "22.2.4",
|
|
44
|
+
type: WrComment,
|
|
45
|
+
isStandalone: true,
|
|
46
|
+
selector: "wr-comment",
|
|
47
|
+
inputs: {
|
|
48
|
+
author: {
|
|
49
|
+
classPropertyName: "author",
|
|
50
|
+
publicName: "author",
|
|
51
|
+
isSignal: true,
|
|
52
|
+
isRequired: false,
|
|
53
|
+
transformFunction: null
|
|
54
|
+
},
|
|
55
|
+
time: {
|
|
56
|
+
classPropertyName: "time",
|
|
57
|
+
publicName: "time",
|
|
58
|
+
isSignal: true,
|
|
59
|
+
isRequired: false,
|
|
60
|
+
transformFunction: null
|
|
61
|
+
},
|
|
62
|
+
datetime: {
|
|
63
|
+
classPropertyName: "datetime",
|
|
64
|
+
publicName: "datetime",
|
|
65
|
+
isSignal: true,
|
|
66
|
+
isRequired: false,
|
|
67
|
+
transformFunction: null
|
|
68
|
+
},
|
|
69
|
+
variant: {
|
|
70
|
+
classPropertyName: "variant",
|
|
71
|
+
publicName: "variant",
|
|
72
|
+
isSignal: true,
|
|
73
|
+
isRequired: false,
|
|
74
|
+
transformFunction: null
|
|
75
|
+
},
|
|
76
|
+
side: {
|
|
77
|
+
classPropertyName: "side",
|
|
78
|
+
publicName: "side",
|
|
79
|
+
isSignal: true,
|
|
80
|
+
isRequired: false,
|
|
81
|
+
transformFunction: null
|
|
82
|
+
},
|
|
83
|
+
repliesLabel: {
|
|
84
|
+
classPropertyName: "repliesLabel",
|
|
85
|
+
publicName: "repliesLabel",
|
|
86
|
+
isSignal: true,
|
|
87
|
+
isRequired: false,
|
|
88
|
+
transformFunction: null
|
|
89
|
+
}
|
|
90
|
+
},
|
|
91
|
+
host: {
|
|
92
|
+
attributes: { "role": "article" },
|
|
93
|
+
properties: {
|
|
94
|
+
"class": "classes()",
|
|
95
|
+
"attr.aria-label": "author() || null"
|
|
96
|
+
}
|
|
97
|
+
},
|
|
98
|
+
ngImport: i0,
|
|
99
|
+
template: "<div class=\"wr-comment__avatar\">\n <ng-content select=\"wr-avatar, [wrCommentAvatar]\" />\n</div>\n\n<div class=\"wr-comment__main\">\n <div class=\"wr-comment__head\">\n @if (author(); as name) {\n <span class=\"wr-comment__author\">{{ name }}</span>\n }\n <ng-content select=\"[wrCommentAuthor]\" />\n @if (time(); as printed) {\n @if (isoDatetime(); as iso) {\n <time class=\"wr-comment__time\" [attr.datetime]=\"iso\">{{ printed }}</time>\n } @else {\n <span class=\"wr-comment__time\">{{ printed }}</span>\n }\n }\n </div>\n\n <div class=\"wr-comment__body\"><ng-content /></div>\n\n <div class=\"wr-comment__actions\">\n <ng-content select=\"[wrCommentActions]\" />\n </div>\n</div>\n\n<div class=\"wr-comment__replies\" role=\"group\" [attr.aria-label]=\"resolvedRepliesLabel()\">\n <ng-content select=\"wr-comment, [wrCommentReplies]\" />\n</div>\n",
|
|
100
|
+
encapsulation: i0.ViewEncapsulation.None
|
|
101
|
+
});
|
|
102
|
+
};
|
|
103
|
+
i0.ɵɵngDeclareClassMetadata({
|
|
104
|
+
minVersion: "12.0.0",
|
|
105
|
+
version: "22.2.4",
|
|
106
|
+
ngImport: i0,
|
|
107
|
+
type: WrComment,
|
|
108
|
+
decorators: [{
|
|
109
|
+
type: Component,
|
|
110
|
+
args: [{
|
|
111
|
+
selector: "wr-comment",
|
|
112
|
+
encapsulation: ViewEncapsulation.None,
|
|
113
|
+
host: {
|
|
114
|
+
"[class]": "classes()",
|
|
115
|
+
role: "article",
|
|
116
|
+
"[attr.aria-label]": "author() || null"
|
|
117
|
+
},
|
|
118
|
+
template: "<div class=\"wr-comment__avatar\">\n <ng-content select=\"wr-avatar, [wrCommentAvatar]\" />\n</div>\n\n<div class=\"wr-comment__main\">\n <div class=\"wr-comment__head\">\n @if (author(); as name) {\n <span class=\"wr-comment__author\">{{ name }}</span>\n }\n <ng-content select=\"[wrCommentAuthor]\" />\n @if (time(); as printed) {\n @if (isoDatetime(); as iso) {\n <time class=\"wr-comment__time\" [attr.datetime]=\"iso\">{{ printed }}</time>\n } @else {\n <span class=\"wr-comment__time\">{{ printed }}</span>\n }\n }\n </div>\n\n <div class=\"wr-comment__body\"><ng-content /></div>\n\n <div class=\"wr-comment__actions\">\n <ng-content select=\"[wrCommentActions]\" />\n </div>\n</div>\n\n<div class=\"wr-comment__replies\" role=\"group\" [attr.aria-label]=\"resolvedRepliesLabel()\">\n <ng-content select=\"wr-comment, [wrCommentReplies]\" />\n</div>\n"
|
|
119
|
+
}]
|
|
120
|
+
}],
|
|
121
|
+
propDecorators: {
|
|
122
|
+
author: [{
|
|
123
|
+
type: i0.Input,
|
|
124
|
+
args: [{
|
|
125
|
+
isSignal: true,
|
|
126
|
+
alias: "author",
|
|
127
|
+
required: false
|
|
128
|
+
}]
|
|
129
|
+
}],
|
|
130
|
+
time: [{
|
|
131
|
+
type: i0.Input,
|
|
132
|
+
args: [{
|
|
133
|
+
isSignal: true,
|
|
134
|
+
alias: "time",
|
|
135
|
+
required: false
|
|
136
|
+
}]
|
|
137
|
+
}],
|
|
138
|
+
datetime: [{
|
|
139
|
+
type: i0.Input,
|
|
140
|
+
args: [{
|
|
141
|
+
isSignal: true,
|
|
142
|
+
alias: "datetime",
|
|
143
|
+
required: false
|
|
144
|
+
}]
|
|
145
|
+
}],
|
|
146
|
+
variant: [{
|
|
147
|
+
type: i0.Input,
|
|
148
|
+
args: [{
|
|
149
|
+
isSignal: true,
|
|
150
|
+
alias: "variant",
|
|
151
|
+
required: false
|
|
152
|
+
}]
|
|
153
|
+
}],
|
|
154
|
+
side: [{
|
|
155
|
+
type: i0.Input,
|
|
156
|
+
args: [{
|
|
157
|
+
isSignal: true,
|
|
158
|
+
alias: "side",
|
|
159
|
+
required: false
|
|
160
|
+
}]
|
|
161
|
+
}],
|
|
162
|
+
repliesLabel: [{
|
|
163
|
+
type: i0.Input,
|
|
164
|
+
args: [{
|
|
165
|
+
isSignal: true,
|
|
166
|
+
alias: "repliesLabel",
|
|
167
|
+
required: false
|
|
168
|
+
}]
|
|
169
|
+
}]
|
|
170
|
+
}
|
|
171
|
+
});
|
|
172
|
+
/**
|
|
173
|
+
* @license
|
|
174
|
+
*
|
|
175
|
+
* Use of this source code is governed by an MIT-style license that can be
|
|
176
|
+
* found in the LICENSE file at https://github.com/thekhegay/ngwr/blob/main/LICENSE
|
|
177
|
+
*/
|
|
178
|
+
var WrCommentComposer = class WrCommentComposer {
|
|
179
|
+
placeholder = input("", ...ngDevMode ? [{ debugName: "placeholder" }] : /* istanbul ignore next */ []);
|
|
180
|
+
ariaLabel = input(null, ...ngDevMode ? [{ debugName: "ariaLabel" }] : /* istanbul ignore next */ []);
|
|
181
|
+
sendLabel = input(null, ...ngDevMode ? [{ debugName: "sendLabel" }] : /* istanbul ignore next */ []);
|
|
182
|
+
rows = input(1, {
|
|
183
|
+
...ngDevMode ? { debugName: "rows" } : /* istanbul ignore next */ {},
|
|
184
|
+
transform: (v) => coerceNumberProperty(v, 1)
|
|
185
|
+
});
|
|
186
|
+
maxRows = input(6, {
|
|
187
|
+
...ngDevMode ? { debugName: "maxRows" } : /* istanbul ignore next */ {},
|
|
188
|
+
transform: (v) => coerceNumberProperty(v, 6)
|
|
189
|
+
});
|
|
190
|
+
disabled = input(false, {
|
|
191
|
+
...ngDevMode ? { debugName: "disabled" } : /* istanbul ignore next */ {},
|
|
192
|
+
transform: coerceBooleanProperty
|
|
193
|
+
});
|
|
194
|
+
readonly = input(false, {
|
|
195
|
+
...ngDevMode ? { debugName: "readonly" } : /* istanbul ignore next */ {},
|
|
196
|
+
transform: coerceBooleanProperty
|
|
197
|
+
});
|
|
198
|
+
sending = input(false, {
|
|
199
|
+
...ngDevMode ? { debugName: "sending" } : /* istanbul ignore next */ {},
|
|
200
|
+
transform: coerceBooleanProperty
|
|
201
|
+
});
|
|
202
|
+
enterSends = input(true, {
|
|
203
|
+
...ngDevMode ? { debugName: "enterSends" } : /* istanbul ignore next */ {},
|
|
204
|
+
transform: coerceBooleanProperty
|
|
205
|
+
});
|
|
206
|
+
clearOnSend = input(true, {
|
|
207
|
+
...ngDevMode ? { debugName: "clearOnSend" } : /* istanbul ignore next */ {},
|
|
208
|
+
transform: coerceBooleanProperty
|
|
209
|
+
});
|
|
210
|
+
value = model("", ...ngDevMode ? [{ debugName: "value" }] : /* istanbul ignore next */ []);
|
|
211
|
+
touch = output();
|
|
212
|
+
send = output();
|
|
213
|
+
resolvedSendLabel = useI18nText(this.sendLabel, "comment.send", "Send");
|
|
214
|
+
canSend = computed(() => this.value().trim() !== "" && !this.disabled() && !this.readonly() && !this.sending(), ...ngDevMode ? [{ debugName: "canSend" }] : /* istanbul ignore next */ []);
|
|
215
|
+
classes = computed(() => {
|
|
216
|
+
const parts = ["wr-comment-composer"];
|
|
217
|
+
if (this.disabled()) parts.push("wr-comment-composer--disabled");
|
|
218
|
+
if (this.readonly()) parts.push("wr-comment-composer--readonly");
|
|
219
|
+
if (this.sending()) parts.push("wr-comment-composer--sending");
|
|
220
|
+
return parts.join(" ");
|
|
221
|
+
}, ...ngDevMode ? [{ debugName: "classes" }] : /* istanbul ignore next */ []);
|
|
222
|
+
field = viewChild.required(WrTextarea, ...ngDevMode ? [{ debugName: "field" }] : /* istanbul ignore next */ []);
|
|
223
|
+
host = inject(ElementRef);
|
|
224
|
+
focus(options) {
|
|
225
|
+
this.field().focus(options);
|
|
226
|
+
}
|
|
227
|
+
submit() {
|
|
228
|
+
if (!this.canSend()) return;
|
|
229
|
+
const text = this.value().trim();
|
|
230
|
+
const hadFocus = this.host.nativeElement.contains(this.host.nativeElement.ownerDocument.activeElement);
|
|
231
|
+
if (this.clearOnSend()) this.value.set("");
|
|
232
|
+
this.send.emit(text);
|
|
233
|
+
if (hadFocus && this.host.nativeElement.contains(this.host.nativeElement.ownerDocument.activeElement)) this.focus();
|
|
234
|
+
}
|
|
235
|
+
onKeydown(event) {
|
|
236
|
+
if (event.key !== KEYS.ENTER || isComposing(event)) return;
|
|
237
|
+
if (!(event.ctrlKey || event.metaKey) && (event.shiftKey || event.altKey || !this.enterSends())) return;
|
|
238
|
+
event.preventDefault();
|
|
239
|
+
this.submit();
|
|
240
|
+
}
|
|
241
|
+
static ɵfac = i0.ɵɵngDeclareFactory({
|
|
242
|
+
minVersion: "12.0.0",
|
|
243
|
+
version: "22.2.4",
|
|
244
|
+
ngImport: i0,
|
|
245
|
+
type: WrCommentComposer,
|
|
246
|
+
deps: [],
|
|
247
|
+
target: i0.ɵɵFactoryTarget.Component
|
|
248
|
+
});
|
|
249
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({
|
|
250
|
+
minVersion: "17.2.0",
|
|
251
|
+
version: "22.2.4",
|
|
252
|
+
type: WrCommentComposer,
|
|
253
|
+
isStandalone: true,
|
|
254
|
+
selector: "wr-comment-composer",
|
|
255
|
+
inputs: {
|
|
256
|
+
placeholder: {
|
|
257
|
+
classPropertyName: "placeholder",
|
|
258
|
+
publicName: "placeholder",
|
|
259
|
+
isSignal: true,
|
|
260
|
+
isRequired: false,
|
|
261
|
+
transformFunction: null
|
|
262
|
+
},
|
|
263
|
+
ariaLabel: {
|
|
264
|
+
classPropertyName: "ariaLabel",
|
|
265
|
+
publicName: "ariaLabel",
|
|
266
|
+
isSignal: true,
|
|
267
|
+
isRequired: false,
|
|
268
|
+
transformFunction: null
|
|
269
|
+
},
|
|
270
|
+
sendLabel: {
|
|
271
|
+
classPropertyName: "sendLabel",
|
|
272
|
+
publicName: "sendLabel",
|
|
273
|
+
isSignal: true,
|
|
274
|
+
isRequired: false,
|
|
275
|
+
transformFunction: null
|
|
276
|
+
},
|
|
277
|
+
rows: {
|
|
278
|
+
classPropertyName: "rows",
|
|
279
|
+
publicName: "rows",
|
|
280
|
+
isSignal: true,
|
|
281
|
+
isRequired: false,
|
|
282
|
+
transformFunction: null
|
|
283
|
+
},
|
|
284
|
+
maxRows: {
|
|
285
|
+
classPropertyName: "maxRows",
|
|
286
|
+
publicName: "maxRows",
|
|
287
|
+
isSignal: true,
|
|
288
|
+
isRequired: false,
|
|
289
|
+
transformFunction: null
|
|
290
|
+
},
|
|
291
|
+
disabled: {
|
|
292
|
+
classPropertyName: "disabled",
|
|
293
|
+
publicName: "disabled",
|
|
294
|
+
isSignal: true,
|
|
295
|
+
isRequired: false,
|
|
296
|
+
transformFunction: null
|
|
297
|
+
},
|
|
298
|
+
readonly: {
|
|
299
|
+
classPropertyName: "readonly",
|
|
300
|
+
publicName: "readonly",
|
|
301
|
+
isSignal: true,
|
|
302
|
+
isRequired: false,
|
|
303
|
+
transformFunction: null
|
|
304
|
+
},
|
|
305
|
+
sending: {
|
|
306
|
+
classPropertyName: "sending",
|
|
307
|
+
publicName: "sending",
|
|
308
|
+
isSignal: true,
|
|
309
|
+
isRequired: false,
|
|
310
|
+
transformFunction: null
|
|
311
|
+
},
|
|
312
|
+
enterSends: {
|
|
313
|
+
classPropertyName: "enterSends",
|
|
314
|
+
publicName: "enterSends",
|
|
315
|
+
isSignal: true,
|
|
316
|
+
isRequired: false,
|
|
317
|
+
transformFunction: null
|
|
318
|
+
},
|
|
319
|
+
clearOnSend: {
|
|
320
|
+
classPropertyName: "clearOnSend",
|
|
321
|
+
publicName: "clearOnSend",
|
|
322
|
+
isSignal: true,
|
|
323
|
+
isRequired: false,
|
|
324
|
+
transformFunction: null
|
|
325
|
+
},
|
|
326
|
+
value: {
|
|
327
|
+
classPropertyName: "value",
|
|
328
|
+
publicName: "value",
|
|
329
|
+
isSignal: true,
|
|
330
|
+
isRequired: false,
|
|
331
|
+
transformFunction: null
|
|
332
|
+
}
|
|
333
|
+
},
|
|
334
|
+
outputs: {
|
|
335
|
+
value: "valueChange",
|
|
336
|
+
touch: "touch",
|
|
337
|
+
send: "send"
|
|
338
|
+
},
|
|
339
|
+
host: { properties: {
|
|
340
|
+
"class": "classes()",
|
|
341
|
+
"attr.aria-busy": "sending() ? \"true\" : null"
|
|
342
|
+
} },
|
|
343
|
+
viewQueries: [{
|
|
344
|
+
propertyName: "field",
|
|
345
|
+
first: true,
|
|
346
|
+
predicate: WrTextarea,
|
|
347
|
+
descendants: true,
|
|
348
|
+
isSignal: true
|
|
349
|
+
}],
|
|
350
|
+
ngImport: i0,
|
|
351
|
+
template: "<wr-textarea\n class=\"wr-comment-composer__field\"\n autosize\n [rows]=\"rows()\"\n [maxRows]=\"maxRows()\"\n [resizable]=\"false\"\n [placeholder]=\"placeholder()\"\n [ariaLabel]=\"ariaLabel()\"\n [disabled]=\"disabled()\"\n [readonly]=\"readonly()\"\n [(value)]=\"value\"\n (touch)=\"touch.emit()\"\n (keydown)=\"onKeydown($event)\"\n/>\n\n<div class=\"wr-comment-composer__actions\">\n <ng-content select=\"[wrComposerActions]\" />\n</div>\n\n<button\n wr-btn\n type=\"button\"\n color=\"primary\"\n shape=\"pill\"\n class=\"wr-comment-composer__send\"\n [attr.aria-label]=\"resolvedSendLabel()\"\n [disabled]=\"!canSend()\"\n (mousedown)=\"$event.preventDefault()\"\n (click)=\"submit()\"\n>\n <svg\n class=\"wr-icon__svg\"\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n aria-hidden=\"true\"\n >\n <path\n d=\"M3.714 3.048a.498.498 0 0 0-.683.627l2.843 7.627a2 2 0 0 1 0 1.396l-2.842 7.627a.498.498 0 0 0 .682.627l18-8.5a.5.5 0 0 0 0-.904z\"\n />\n <path d=\"M6 12h16\" />\n </svg>\n</button>\n",
|
|
352
|
+
dependencies: [{
|
|
353
|
+
kind: "component",
|
|
354
|
+
type: WrButton,
|
|
355
|
+
selector: "wr-btn, button[wr-btn], a[wr-btn]",
|
|
356
|
+
inputs: [
|
|
357
|
+
"color",
|
|
358
|
+
"size",
|
|
359
|
+
"shape",
|
|
360
|
+
"icon",
|
|
361
|
+
"iconPosition",
|
|
362
|
+
"disabled",
|
|
363
|
+
"outlined",
|
|
364
|
+
"block",
|
|
365
|
+
"loading",
|
|
366
|
+
"disabledWhenLoading"
|
|
367
|
+
]
|
|
368
|
+
}, {
|
|
369
|
+
kind: "component",
|
|
370
|
+
type: WrTextarea,
|
|
371
|
+
selector: "wr-textarea",
|
|
372
|
+
inputs: [
|
|
373
|
+
"placeholder",
|
|
374
|
+
"ariaLabel",
|
|
375
|
+
"size",
|
|
376
|
+
"rows",
|
|
377
|
+
"resizable",
|
|
378
|
+
"resize",
|
|
379
|
+
"readonly",
|
|
380
|
+
"autosize",
|
|
381
|
+
"maxRows",
|
|
382
|
+
"disabled",
|
|
383
|
+
"value"
|
|
384
|
+
],
|
|
385
|
+
outputs: ["valueChange", "touch"]
|
|
386
|
+
}],
|
|
387
|
+
encapsulation: i0.ViewEncapsulation.None
|
|
388
|
+
});
|
|
389
|
+
};
|
|
390
|
+
i0.ɵɵngDeclareClassMetadata({
|
|
391
|
+
minVersion: "12.0.0",
|
|
392
|
+
version: "22.2.4",
|
|
393
|
+
ngImport: i0,
|
|
394
|
+
type: WrCommentComposer,
|
|
395
|
+
decorators: [{
|
|
396
|
+
type: Component,
|
|
397
|
+
args: [{
|
|
398
|
+
selector: "wr-comment-composer",
|
|
399
|
+
imports: [WrButton, WrTextarea],
|
|
400
|
+
encapsulation: ViewEncapsulation.None,
|
|
401
|
+
host: {
|
|
402
|
+
"[class]": "classes()",
|
|
403
|
+
"[attr.aria-busy]": "sending() ? \"true\" : null"
|
|
404
|
+
},
|
|
405
|
+
template: "<wr-textarea\n class=\"wr-comment-composer__field\"\n autosize\n [rows]=\"rows()\"\n [maxRows]=\"maxRows()\"\n [resizable]=\"false\"\n [placeholder]=\"placeholder()\"\n [ariaLabel]=\"ariaLabel()\"\n [disabled]=\"disabled()\"\n [readonly]=\"readonly()\"\n [(value)]=\"value\"\n (touch)=\"touch.emit()\"\n (keydown)=\"onKeydown($event)\"\n/>\n\n<div class=\"wr-comment-composer__actions\">\n <ng-content select=\"[wrComposerActions]\" />\n</div>\n\n<button\n wr-btn\n type=\"button\"\n color=\"primary\"\n shape=\"pill\"\n class=\"wr-comment-composer__send\"\n [attr.aria-label]=\"resolvedSendLabel()\"\n [disabled]=\"!canSend()\"\n (mousedown)=\"$event.preventDefault()\"\n (click)=\"submit()\"\n>\n <svg\n class=\"wr-icon__svg\"\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n aria-hidden=\"true\"\n >\n <path\n d=\"M3.714 3.048a.498.498 0 0 0-.683.627l2.843 7.627a2 2 0 0 1 0 1.396l-2.842 7.627a.498.498 0 0 0 .682.627l18-8.5a.5.5 0 0 0 0-.904z\"\n />\n <path d=\"M6 12h16\" />\n </svg>\n</button>\n"
|
|
406
|
+
}]
|
|
407
|
+
}],
|
|
408
|
+
propDecorators: {
|
|
409
|
+
placeholder: [{
|
|
410
|
+
type: i0.Input,
|
|
411
|
+
args: [{
|
|
412
|
+
isSignal: true,
|
|
413
|
+
alias: "placeholder",
|
|
414
|
+
required: false
|
|
415
|
+
}]
|
|
416
|
+
}],
|
|
417
|
+
ariaLabel: [{
|
|
418
|
+
type: i0.Input,
|
|
419
|
+
args: [{
|
|
420
|
+
isSignal: true,
|
|
421
|
+
alias: "ariaLabel",
|
|
422
|
+
required: false
|
|
423
|
+
}]
|
|
424
|
+
}],
|
|
425
|
+
sendLabel: [{
|
|
426
|
+
type: i0.Input,
|
|
427
|
+
args: [{
|
|
428
|
+
isSignal: true,
|
|
429
|
+
alias: "sendLabel",
|
|
430
|
+
required: false
|
|
431
|
+
}]
|
|
432
|
+
}],
|
|
433
|
+
rows: [{
|
|
434
|
+
type: i0.Input,
|
|
435
|
+
args: [{
|
|
436
|
+
isSignal: true,
|
|
437
|
+
alias: "rows",
|
|
438
|
+
required: false
|
|
439
|
+
}]
|
|
440
|
+
}],
|
|
441
|
+
maxRows: [{
|
|
442
|
+
type: i0.Input,
|
|
443
|
+
args: [{
|
|
444
|
+
isSignal: true,
|
|
445
|
+
alias: "maxRows",
|
|
446
|
+
required: false
|
|
447
|
+
}]
|
|
448
|
+
}],
|
|
449
|
+
disabled: [{
|
|
450
|
+
type: i0.Input,
|
|
451
|
+
args: [{
|
|
452
|
+
isSignal: true,
|
|
453
|
+
alias: "disabled",
|
|
454
|
+
required: false
|
|
455
|
+
}]
|
|
456
|
+
}],
|
|
457
|
+
readonly: [{
|
|
458
|
+
type: i0.Input,
|
|
459
|
+
args: [{
|
|
460
|
+
isSignal: true,
|
|
461
|
+
alias: "readonly",
|
|
462
|
+
required: false
|
|
463
|
+
}]
|
|
464
|
+
}],
|
|
465
|
+
sending: [{
|
|
466
|
+
type: i0.Input,
|
|
467
|
+
args: [{
|
|
468
|
+
isSignal: true,
|
|
469
|
+
alias: "sending",
|
|
470
|
+
required: false
|
|
471
|
+
}]
|
|
472
|
+
}],
|
|
473
|
+
enterSends: [{
|
|
474
|
+
type: i0.Input,
|
|
475
|
+
args: [{
|
|
476
|
+
isSignal: true,
|
|
477
|
+
alias: "enterSends",
|
|
478
|
+
required: false
|
|
479
|
+
}]
|
|
480
|
+
}],
|
|
481
|
+
clearOnSend: [{
|
|
482
|
+
type: i0.Input,
|
|
483
|
+
args: [{
|
|
484
|
+
isSignal: true,
|
|
485
|
+
alias: "clearOnSend",
|
|
486
|
+
required: false
|
|
487
|
+
}]
|
|
488
|
+
}],
|
|
489
|
+
value: [{
|
|
490
|
+
type: i0.Input,
|
|
491
|
+
args: [{
|
|
492
|
+
isSignal: true,
|
|
493
|
+
alias: "value",
|
|
494
|
+
required: false
|
|
495
|
+
}]
|
|
496
|
+
}, {
|
|
497
|
+
type: i0.Output,
|
|
498
|
+
args: ["valueChange"]
|
|
499
|
+
}],
|
|
500
|
+
touch: [{
|
|
501
|
+
type: i0.Output,
|
|
502
|
+
args: ["touch"]
|
|
503
|
+
}],
|
|
504
|
+
send: [{
|
|
505
|
+
type: i0.Output,
|
|
506
|
+
args: ["send"]
|
|
507
|
+
}],
|
|
508
|
+
field: [{
|
|
509
|
+
type: i0.ViewChild,
|
|
510
|
+
args: [i0.forwardRef(() => WrTextarea), { isSignal: true }]
|
|
511
|
+
}]
|
|
512
|
+
}
|
|
513
|
+
});
|
|
514
|
+
export { WrComment, WrCommentComposer };
|
|
515
|
+
|
|
516
|
+
//# sourceMappingURL=ngwr-comment.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ngwr-comment.mjs","names":[],"sources":["../../../projects/lib/comment/comment.ts","../../../projects/lib/comment/comment.html","../../../projects/lib/comment/comment-composer.ts","../../../projects/lib/comment/comment-composer.html"],"sourcesContent":["/**\n * @license\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://github.com/thekhegay/ngwr/blob/main/LICENSE\n */\n\nimport { Component, ViewEncapsulation, computed, input } from '@angular/core';\n\nimport { useI18nText } from 'ngwr/i18n';\n\nimport type { WrCommentSide, WrCommentVariant } from './types';\n\n/**\n * One entry in a discussion or a chat: avatar, author, time, body, actions and\n * nested replies. The data stays with the app: the component takes no array,\n * fetches nothing and sorts nothing. Everything is projected or passed as a\n * string, and the app formats the time itself (`wrDate` is the usual route).\n *\n * Slots, each optional:\n *\n * - `<wr-avatar>` or `[wrCommentAvatar]` for the avatar column\n * - `[wrCommentAuthor]` for an author drawn by the app (a profile link) in\n * place of the `author` input\n * - default content for the body\n * - `[wrCommentActions]` for the action row under the body\n * - nested `<wr-comment>` elements, or a `[wrCommentReplies]` wrapper, for\n * replies; they indent twice, and deeper levels sit flush with their parent\n * so a phone keeps room for the body\n *\n * The host is an `article` named after the author, replies sit in a `group`\n * named by `repliesLabel`, and no heading level is assumed: the page owns the\n * outline. Wrap a thread in a list and a chat in `role=\"log\"`.\n *\n * `variant=\"bubble\"` draws the body as a chat bubble; `side=\"end\"` puts it on\n * the reader's own side of the log.\n *\n * @example\n * ```html\n * <wr-comment author=\"Ann\" time=\"2 hours ago\" [datetime]=\"posted\">\n * <wr-avatar size=\"2.5rem\" alt=\"\">AN</wr-avatar>\n * Looks good to me.\n * <div wrCommentActions><button wr-btn size=\"sm\">Reply</button></div>\n * <wr-comment author=\"Bob\" time=\"an hour ago\">Thanks!</wr-comment>\n * </wr-comment>\n *\n * <wr-comment variant=\"bubble\" side=\"end\" time=\"09:41\">On my way.</wr-comment>\n * ```\n *\n * @see https://ngwr.dev/reference/components/comment\n */\n@Component({\n selector: 'wr-comment',\n templateUrl: './comment.html',\n encapsulation: ViewEncapsulation.None,\n host: {\n '[class]': 'classes()',\n role: 'article',\n '[attr.aria-label]': 'author() || null',\n },\n})\nexport class WrComment {\n /**\n * Who wrote it. Also the article's accessible name. Leave it empty when a\n * `[wrCommentAuthor]` element draws the name instead.\n *\n * @default ''\n */\n readonly author = input<string>('');\n\n /**\n * When it was written, already formatted: the component does no date\n * formatting. Run a `Date` through the `wrDate` pipe on the way in.\n *\n * @default ''\n */\n readonly time = input<string>('');\n\n /**\n * The same moment, machine-readable. With it the time renders as a\n * `<time datetime>`: a `Date` is written as ISO 8601, a string as given.\n *\n * @default null\n */\n readonly datetime = input<Date | string | null>(null);\n\n /**\n * `row` is a discussion entry; `bubble` is a chat message, which takes no replies.\n *\n * @default 'row'\n */\n readonly variant = input<WrCommentVariant>('row');\n\n /**\n * Which inline side a bubble sits on. `end` is the reader's own message.\n * Ignored by the `row` variant.\n *\n * @default 'start'\n */\n readonly side = input<WrCommentSide>('start');\n\n /** Accessible name of the replies group. Falls back to `comment.replies`, then `'Replies'`. */\n readonly repliesLabel = input<string | null>(null);\n\n protected readonly resolvedRepliesLabel = useI18nText(this.repliesLabel, 'comment.replies', 'Replies');\n\n protected readonly isoDatetime = computed(() => {\n const value = this.datetime();\n if (value === null) return null;\n if (value instanceof Date) return Number.isNaN(value.getTime()) ? null : value.toISOString();\n return value || null;\n });\n\n protected readonly classes = computed(() => {\n const parts = ['wr-comment'];\n if (this.variant() === 'bubble') parts.push('wr-comment--bubble', `wr-comment--${this.side()}`);\n return parts.join(' ');\n });\n}\n","<div class=\"wr-comment__avatar\">\n <ng-content select=\"wr-avatar, [wrCommentAvatar]\" />\n</div>\n\n<div class=\"wr-comment__main\">\n <div class=\"wr-comment__head\">\n @if (author(); as name) {\n <span class=\"wr-comment__author\">{{ name }}</span>\n }\n <ng-content select=\"[wrCommentAuthor]\" />\n @if (time(); as printed) {\n @if (isoDatetime(); as iso) {\n <time class=\"wr-comment__time\" [attr.datetime]=\"iso\">{{ printed }}</time>\n } @else {\n <span class=\"wr-comment__time\">{{ printed }}</span>\n }\n }\n </div>\n\n <div class=\"wr-comment__body\"><ng-content /></div>\n\n <div class=\"wr-comment__actions\">\n <ng-content select=\"[wrCommentActions]\" />\n </div>\n</div>\n\n<div class=\"wr-comment__replies\" role=\"group\" [attr.aria-label]=\"resolvedRepliesLabel()\">\n <ng-content select=\"wr-comment, [wrCommentReplies]\" />\n</div>\n","/**\n * @license\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://github.com/thekhegay/ngwr/blob/main/LICENSE\n */\n\nimport { coerceBooleanProperty, coerceNumberProperty } from '@angular/cdk/coercion';\nimport {\n Component,\n ElementRef,\n ViewEncapsulation,\n computed,\n inject,\n input,\n model,\n output,\n viewChild,\n} from '@angular/core';\nimport type { FormValueControl } from '@angular/forms/signals';\n\nimport { WrButton } from 'ngwr/button';\nimport { useI18nText } from 'ngwr/i18n';\nimport { WrTextarea } from 'ngwr/textarea';\nimport { KEYS, isComposing } from 'ngwr/utils';\n\n/**\n * A chat composer: an autosizing field with a send button. Enter sends,\n * Shift+Enter adds a line, Ctrl+Enter and Cmd+Enter always send. The text is\n * emitted through `send` and the composer decides nothing about where it goes.\n *\n * A signal-forms native control: it implements `FormValueControl<string>`, so\n * `[formField]` binds straight to its `value` model. `[(value)]` works\n * standalone.\n *\n * What sends and what does not:\n *\n * - Whitespace-only text never sends; the button is disabled until there is\n * something to send, and Enter does nothing.\n * - `disabled`, `readonly` and `sending` all block sending and keep the text.\n * `sending` also marks the composer `aria-busy`; the field stays editable so\n * the next message can be typed.\n * - An Enter that confirms an IME composition never sends. Both signals are\n * read, `isComposing` and `keyCode === 229`, because Safari fires\n * `compositionend` before the keydown, so `isComposing` is already `false`\n * on the committing keystroke.\n *\n * The text is trimmed and, with `clearOnSend` (the default), the field is\n * cleared BEFORE `send` emits: an app whose request fails puts the text back\n * through the composer's own model, `composer.value.set(text)`, at any time,\n * and nothing overwrites it. A model bound with `[(value)]` takes that write\n * only once change detection has run in between: written back synchronously\n * from the handler, the bound expression still holds the value Angular last\n * bound, so the input is skipped. An app that would rather keep the text until\n * the request succeeds sets `[clearOnSend]=\"false\"` and clears the model\n * itself. Focus stays in the field after sending, unless the `send` handler\n * moved it.\n *\n * @example\n * ```html\n * <wr-comment-composer placeholder=\"Message\" [(value)]=\"draft\" (send)=\"post($event)\" />\n *\n * <wr-comment-composer [sending]=\"posting()\" [clearOnSend]=\"false\" (send)=\"post($event)\">\n * <button wrComposerActions wr-btn size=\"sm\">Attach</button>\n * </wr-comment-composer>\n * ```\n *\n * @see https://ngwr.dev/reference/components/comment\n */\n@Component({\n selector: 'wr-comment-composer',\n templateUrl: './comment-composer.html',\n imports: [WrButton, WrTextarea],\n encapsulation: ViewEncapsulation.None,\n host: {\n '[class]': 'classes()',\n '[attr.aria-busy]': 'sending() ? \"true\" : null',\n },\n})\nexport class WrCommentComposer implements FormValueControl<string> {\n /** Native placeholder text. @default '' */\n readonly placeholder = input<string>('');\n\n /**\n * Accessible name for the field. Falls back to the placeholder: the native\n * `<textarea>` lives inside the component, so a `<label for>` outside cannot\n * reach it.\n */\n readonly ariaLabel = input<string | null>(null);\n\n /** Accessible name of the send button. Falls back to `comment.send`, then `'Send'`. */\n readonly sendLabel = input<string | null>(null);\n\n /** Visible row count while the field is empty. @default 1 */\n readonly rows = input(1, { transform: (v: unknown): number => coerceNumberProperty(v, 1) });\n\n /** Rows the field grows to before it scrolls on its own. @default 6 */\n readonly maxRows = input(6, { transform: (v: unknown): number => coerceNumberProperty(v, 6) });\n\n /**\n * Disable the field and the button. Bound automatically from the field's\n * disabled state when used with `[formField]`. The text is kept.\n *\n * @default false\n */\n readonly disabled = input(false, { transform: coerceBooleanProperty });\n\n /**\n * Read-only state: the text is shown and announced, keeps its tab stop and\n * cannot be edited or sent.\n *\n * @default false\n */\n readonly readonly = input(false, { transform: coerceBooleanProperty });\n\n /**\n * A send is in flight: the button is disabled, the host is `aria-busy` and\n * Enter does nothing. The field stays editable and the text is kept.\n *\n * @default false\n */\n readonly sending = input(false, { transform: coerceBooleanProperty });\n\n /**\n * `true`: Enter sends and Shift+Enter adds a line. `false`: Enter adds a line\n * and only Ctrl+Enter or Cmd+Enter sends.\n *\n * @default true\n */\n readonly enterSends = input(true, { transform: coerceBooleanProperty });\n\n /**\n * Clear the field before `send` emits. With `false` the text stays until the\n * app clears the model itself.\n *\n * @default true\n */\n readonly clearOnSend = input(true, { transform: coerceBooleanProperty });\n\n /** The draft. Bound by `[formField]`, or two-way via `[(value)]`. */\n readonly value = model<string>('');\n\n /** Emitted on blur so a bound field can mark itself touched. */\n readonly touch = output<void>();\n\n /** The trimmed text, once per send. Where it goes is the app's business. */\n readonly send = output<string>();\n\n protected readonly resolvedSendLabel = useI18nText(this.sendLabel, 'comment.send', 'Send');\n\n /** Whether a send would go through right now. */\n protected readonly canSend = computed(\n () => this.value().trim() !== '' && !this.disabled() && !this.readonly() && !this.sending()\n );\n\n protected readonly classes = computed(() => {\n const parts = ['wr-comment-composer'];\n if (this.disabled()) parts.push('wr-comment-composer--disabled');\n if (this.readonly()) parts.push('wr-comment-composer--readonly');\n if (this.sending()) parts.push('wr-comment-composer--sending');\n return parts.join(' ');\n });\n\n private readonly field = viewChild.required(WrTextarea);\n private readonly host = inject<ElementRef<HTMLElement>>(ElementRef);\n\n /** Focus the field, which is what Signal Forms calls to focus this control. */\n focus(options?: FocusOptions): void {\n this.field().focus(options);\n }\n\n /**\n * Send the current text, exactly as the button and Enter do: nothing happens\n * for whitespace-only text or while `disabled`, `readonly` or `sending`.\n */\n submit(): void {\n if (!this.canSend()) return;\n const text = this.value().trim();\n const hadFocus = this.host.nativeElement.contains(this.host.nativeElement.ownerDocument.activeElement);\n if (this.clearOnSend()) this.value.set('');\n this.send.emit(text);\n // A keyboard user is on the button, which sending just disabled: hand focus\n // back unless the handler moved it. A pointer never took it (see the template).\n if (hadFocus && this.host.nativeElement.contains(this.host.nativeElement.ownerDocument.activeElement)) {\n this.focus();\n }\n }\n\n protected onKeydown(event: KeyboardEvent): void {\n if (event.key !== KEYS.ENTER || isComposing(event)) return;\n const modifier = event.ctrlKey || event.metaKey;\n if (!modifier && (event.shiftKey || event.altKey || !this.enterSends())) return;\n // A sending Enter never reaches the field: on empty text it would only add\n // blank lines that the next send trims away.\n event.preventDefault();\n this.submit();\n }\n}\n","<wr-textarea\n class=\"wr-comment-composer__field\"\n autosize\n [rows]=\"rows()\"\n [maxRows]=\"maxRows()\"\n [resizable]=\"false\"\n [placeholder]=\"placeholder()\"\n [ariaLabel]=\"ariaLabel()\"\n [disabled]=\"disabled()\"\n [readonly]=\"readonly()\"\n [(value)]=\"value\"\n (touch)=\"touch.emit()\"\n (keydown)=\"onKeydown($event)\"\n/>\n\n<div class=\"wr-comment-composer__actions\">\n <ng-content select=\"[wrComposerActions]\" />\n</div>\n\n<button\n wr-btn\n type=\"button\"\n color=\"primary\"\n shape=\"pill\"\n class=\"wr-comment-composer__send\"\n [attr.aria-label]=\"resolvedSendLabel()\"\n [disabled]=\"!canSend()\"\n (mousedown)=\"$event.preventDefault()\"\n (click)=\"submit()\"\n>\n <svg\n class=\"wr-icon__svg\"\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n aria-hidden=\"true\"\n >\n <path\n d=\"M3.714 3.048a.498.498 0 0 0-.683.627l2.843 7.627a2 2 0 0 1 0 1.396l-2.842 7.627a.498.498 0 0 0 .682.627l18-8.5a.5.5 0 0 0 0-.904z\"\n />\n <path d=\"M6 12h16\" />\n </svg>\n</button>\n"],"mappings":"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"}
|
|
@@ -37,6 +37,20 @@ var WrDialogHarness = class WrDialogHarness extends ContentContainerComponentHar
|
|
|
37
37
|
async sendEscape() {
|
|
38
38
|
await (await this.host()).sendKeys(TestKey.ESCAPE);
|
|
39
39
|
}
|
|
40
|
+
async isConfirm() {
|
|
41
|
+
return await this.locatorForOptional(".wr-dialog-confirm")() !== null;
|
|
42
|
+
}
|
|
43
|
+
async confirm() {
|
|
44
|
+
await (await this.answer("confirm")).click();
|
|
45
|
+
}
|
|
46
|
+
async cancel() {
|
|
47
|
+
await (await this.answer("cancel")).click();
|
|
48
|
+
}
|
|
49
|
+
async answer(which) {
|
|
50
|
+
const button = await this.locatorForOptional(`.wr-dialog-confirm__action--${which}`)();
|
|
51
|
+
if (!button) throw new Error(`WrDialogHarness: ${which}() answers a WrDialog.confirm() dialog; this one was opened with open()`);
|
|
52
|
+
return button;
|
|
53
|
+
}
|
|
40
54
|
async isFocusTrapped() {
|
|
41
55
|
return (await this.host()).matchesSelector(":focus-within");
|
|
42
56
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ngwr-dialog-testing.mjs","names":[],"sources":["../../../projects/lib/dialog/testing/wr-dialog-harness.ts"],"sourcesContent":["/**\n * @license\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://github.com/thekhegay/ngwr/blob/main/LICENSE\n */\n\nimport { ContentContainerComponentHarness, HarnessPredicate, TestKey } from '@angular/cdk/testing';\n\nimport type { WrDialogHarnessFilters } from './types';\n\n/**\n * Test harness for a dialog opened with `WrDialog.open()`.\n *\n * A dialog is never inside the fixture — `WrDialog` attaches it to the overlay\n * container — so load this from `TestbedHarnessEnvironment.documentRootLoader()`\n * rather than from `loader()`. The harness is a CONTENT CONTAINER, which is what\n * makes the interesting assertions possible: the buttons and fields it holds are\n * the consumer's own components, so `dialog.getHarness(WrButtonHarness…)` reads\n * them without the spec ever touching the overlay.\n *\n * @example\n * ```ts\n * const rootLoader = TestbedHarnessEnvironment.documentRootLoader(fixture);\n * const dialog = await rootLoader.getHarness(WrDialogHarness);\n *\n * expect(await dialog.getTitleText()).toBe('Delete this item?');\n * await (await dialog.getHarness(WrButtonHarness.with({ text: 'Delete' }))).click();\n * ```\n *\n * @see https://ngwr.dev/guides/testing\n */\nexport class WrDialogHarness extends ContentContainerComponentHarness {\n /** The overlay panel `WrDialog` creates. */\n static hostSelector = '.wr-dialog-panel';\n\n /** Build a predicate that narrows the query — useful when two dialogs are stacked. */\n static with(options: WrDialogHarnessFilters = {}): HarnessPredicate<WrDialogHarness> {\n return new HarnessPredicate(WrDialogHarness, options).addOption('title', options.title, (harness, title) =>\n HarnessPredicate.stringMatches(harness.getTitleText(), title)\n );\n }\n\n /** The `[wrDialogTitle]` text, or `null` when the dialog has no title. */\n async getTitleText(): Promise<string | null> {\n const title = await this.locatorForOptional('.wr-dialog__title')();\n return title ? title.text() : null;\n }\n\n /** The `[wrDialogContent]` text, or `null` when the dialog projects no content region. */\n async getContentText(): Promise<string | null> {\n const content = await this.locatorForOptional('.wr-dialog__content')();\n return content ? content.text() : null;\n }\n\n /**\n * The role and modality the dialog announces.\n *\n * Worth asserting rather than assuming: `WrDialog` sets these on the OVERLAY\n * element, not on the component it renders, so a consumer looking for them on\n * their own host would not find them.\n */\n async getRole(): Promise<string | null> {\n return (await this.host()).getAttribute('role');\n }\n\n /** Whether the dialog is marked `aria-modal`. */\n async isModal(): Promise<boolean> {\n return (await (await this.host()).getAttribute('aria-modal')) === 'true';\n }\n\n /** Whether the built-in dismiss button is present (`closable`, the default). */\n async isClosable(): Promise<boolean> {\n return (await this.locatorForOptional('.wr-dialog__close')()) !== null;\n }\n\n /** The dismiss button's accessible name — the string a screen reader reads out. */\n async getCloseLabel(): Promise<string | null> {\n const close = await this.locatorForOptional('.wr-dialog__close')();\n return close ? close.getAttribute('aria-label') : null;\n }\n\n /** Click the built-in dismiss button. Throws when the dialog was opened `closable: false`. */\n async close(): Promise<void> {\n await (await this.locatorFor('.wr-dialog__close')()).click();\n }\n\n /** Press Escape. A dialog opened `closeOnEscape: false` ignores it — assert, do not assume. */\n async sendEscape(): Promise<void> {\n await (await this.host()).sendKeys(TestKey.ESCAPE);\n }\n\n /** Whether focus is currently somewhere inside the dialog, where the trap should keep it. */\n async isFocusTrapped(): Promise<boolean> {\n return (await this.host()).matchesSelector(':focus-within');\n }\n}\n"],"mappings":";;;;;;;AAgCA,IAAa,kBAAb,MAAa,wBAAwB,iCAAgC;CAEnE,OAAO,eAAe;CAGtB,OAAO,KAAK,UAAkC,CAAA,GAAE;EAC9C,OAAO,IAAI,iBAAiB,iBAAiB,OAAO,CAAC,CAAC,UAAU,SAAS,QAAQ,QAAQ,SAAS,UAChG,iBAAiB,cAAc,QAAQ,aAAY,GAAI,KAAK,CAAC;CAEjE;CAGA,MAAM,eAAY;EAChB,MAAM,QAAQ,MAAM,KAAK,mBAAmB,mBAAmB,CAAC,CAAA;EAChE,OAAO,QAAQ,MAAM,KAAI,IAAK;CAChC;CAGA,MAAM,iBAAc;EAClB,MAAM,UAAU,MAAM,KAAK,mBAAmB,qBAAqB,CAAC,CAAA;EACpE,OAAO,UAAU,QAAQ,KAAI,IAAK;CACpC;CASA,MAAM,UAAO;EACX,QAAQ,MAAM,KAAK,KAAI,EAAA,CAAI,aAAa,MAAM;CAChD;CAGA,MAAM,UAAO;EACX,OAAQ,OAAO,MAAM,KAAK,KAAI,EAAA,CAAI,aAAa,YAAY,MAAO;CACpE;CAGA,MAAM,aAAU;EACd,OAAQ,MAAM,KAAK,mBAAmB,mBAAmB,CAAC,CAAA,MAAQ;CACpE;CAGA,MAAM,gBAAa;EACjB,MAAM,QAAQ,MAAM,KAAK,mBAAmB,mBAAmB,CAAC,CAAA;EAChE,OAAO,QAAQ,MAAM,aAAa,YAAY,IAAI;CACpD;CAGA,MAAM,QAAK;EACT,OAAO,MAAM,KAAK,WAAW,mBAAmB,CAAC,CAAA,EAAA,CAAI,MAAK;CAC5D;CAGA,MAAM,aAAU;EACd,OAAO,MAAM,KAAK,KAAI,EAAA,CAAI,SAAS,QAAQ,MAAM;CACnD;CAGA,MAAM,iBAAc;EAClB,QAAQ,MAAM,KAAK,KAAI,EAAA,CAAI,gBAAgB,eAAe;CAC5D"}
|
|
1
|
+
{"version":3,"file":"ngwr-dialog-testing.mjs","names":[],"sources":["../../../projects/lib/dialog/testing/wr-dialog-harness.ts"],"sourcesContent":["/**\n * @license\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://github.com/thekhegay/ngwr/blob/main/LICENSE\n */\n\nimport { ContentContainerComponentHarness, HarnessPredicate, type TestElement, TestKey } from '@angular/cdk/testing';\n\nimport type { WrDialogHarnessFilters } from './types';\n\n/**\n * Test harness for a dialog opened with `WrDialog.open()`.\n *\n * A dialog is never inside the fixture — `WrDialog` attaches it to the overlay\n * container — so load this from `TestbedHarnessEnvironment.documentRootLoader()`\n * rather than from `loader()`. The harness is a CONTENT CONTAINER, which is what\n * makes the interesting assertions possible: the buttons and fields it holds are\n * the consumer's own components, so `dialog.getHarness(WrButtonHarness…)` reads\n * them without the spec ever touching the overlay.\n *\n * @example\n * ```ts\n * const rootLoader = TestbedHarnessEnvironment.documentRootLoader(fixture);\n * const dialog = await rootLoader.getHarness(WrDialogHarness);\n *\n * expect(await dialog.getTitleText()).toBe('Delete this item?');\n * await (await dialog.getHarness(WrButtonHarness.with({ text: 'Delete' }))).click();\n * ```\n *\n * @see https://ngwr.dev/guides/testing\n */\nexport class WrDialogHarness extends ContentContainerComponentHarness {\n /** The overlay panel `WrDialog` creates. */\n static hostSelector = '.wr-dialog-panel';\n\n /** Build a predicate that narrows the query — useful when two dialogs are stacked. */\n static with(options: WrDialogHarnessFilters = {}): HarnessPredicate<WrDialogHarness> {\n return new HarnessPredicate(WrDialogHarness, options).addOption('title', options.title, (harness, title) =>\n HarnessPredicate.stringMatches(harness.getTitleText(), title)\n );\n }\n\n /** The `[wrDialogTitle]` text, or `null` when the dialog has no title. */\n async getTitleText(): Promise<string | null> {\n const title = await this.locatorForOptional('.wr-dialog__title')();\n return title ? title.text() : null;\n }\n\n /** The `[wrDialogContent]` text, or `null` when the dialog projects no content region. */\n async getContentText(): Promise<string | null> {\n const content = await this.locatorForOptional('.wr-dialog__content')();\n return content ? content.text() : null;\n }\n\n /**\n * The role and modality the dialog announces.\n *\n * Worth asserting rather than assuming: `WrDialog` sets these on the OVERLAY\n * element, not on the component it renders, so a consumer looking for them on\n * their own host would not find them.\n */\n async getRole(): Promise<string | null> {\n return (await this.host()).getAttribute('role');\n }\n\n /** Whether the dialog is marked `aria-modal`. */\n async isModal(): Promise<boolean> {\n return (await (await this.host()).getAttribute('aria-modal')) === 'true';\n }\n\n /** Whether the built-in dismiss button is present (`closable`, the default). */\n async isClosable(): Promise<boolean> {\n return (await this.locatorForOptional('.wr-dialog__close')()) !== null;\n }\n\n /** The dismiss button's accessible name — the string a screen reader reads out. */\n async getCloseLabel(): Promise<string | null> {\n const close = await this.locatorForOptional('.wr-dialog__close')();\n return close ? close.getAttribute('aria-label') : null;\n }\n\n /** Click the built-in dismiss button. Throws when the dialog was opened `closable: false`. */\n async close(): Promise<void> {\n await (await this.locatorFor('.wr-dialog__close')()).click();\n }\n\n /** Press Escape. A dialog opened `closeOnEscape: false` ignores it — assert, do not assume. */\n async sendEscape(): Promise<void> {\n await (await this.host()).sendKeys(TestKey.ESCAPE);\n }\n\n /** Whether this is a `WrDialog.confirm()` dialog, the one `confirm()` and `cancel()` answer. */\n async isConfirm(): Promise<boolean> {\n return (await this.locatorForOptional('.wr-dialog-confirm')()) !== null;\n }\n\n /** Press the confirm button of a `WrDialog.confirm()` dialog. Throws on any other dialog. */\n async confirm(): Promise<void> {\n await (await this.answer('confirm')).click();\n }\n\n /** Press the cancel button of a `WrDialog.confirm()` dialog. Throws on any other dialog. */\n async cancel(): Promise<void> {\n await (await this.answer('cancel')).click();\n }\n\n private async answer(which: 'confirm' | 'cancel'): Promise<TestElement> {\n const button = await this.locatorForOptional(`.wr-dialog-confirm__action--${which}`)();\n if (!button) {\n throw new Error(\n `WrDialogHarness: ${which}() answers a WrDialog.confirm() dialog; this one was opened with open()`\n );\n }\n return button;\n }\n\n /** Whether focus is currently somewhere inside the dialog, where the trap should keep it. */\n async isFocusTrapped(): Promise<boolean> {\n return (await this.host()).matchesSelector(':focus-within');\n }\n}\n"],"mappings":";;;;;;;AAgCA,IAAa,kBAAb,MAAa,wBAAwB,iCAAgC;CAEnE,OAAO,eAAe;CAGtB,OAAO,KAAK,UAAkC,CAAA,GAAE;EAC9C,OAAO,IAAI,iBAAiB,iBAAiB,OAAO,CAAC,CAAC,UAAU,SAAS,QAAQ,QAAQ,SAAS,UAChG,iBAAiB,cAAc,QAAQ,aAAY,GAAI,KAAK,CAAC;CAEjE;CAGA,MAAM,eAAY;EAChB,MAAM,QAAQ,MAAM,KAAK,mBAAmB,mBAAmB,CAAC,CAAA;EAChE,OAAO,QAAQ,MAAM,KAAI,IAAK;CAChC;CAGA,MAAM,iBAAc;EAClB,MAAM,UAAU,MAAM,KAAK,mBAAmB,qBAAqB,CAAC,CAAA;EACpE,OAAO,UAAU,QAAQ,KAAI,IAAK;CACpC;CASA,MAAM,UAAO;EACX,QAAQ,MAAM,KAAK,KAAI,EAAA,CAAI,aAAa,MAAM;CAChD;CAGA,MAAM,UAAO;EACX,OAAQ,OAAO,MAAM,KAAK,KAAI,EAAA,CAAI,aAAa,YAAY,MAAO;CACpE;CAGA,MAAM,aAAU;EACd,OAAQ,MAAM,KAAK,mBAAmB,mBAAmB,CAAC,CAAA,MAAQ;CACpE;CAGA,MAAM,gBAAa;EACjB,MAAM,QAAQ,MAAM,KAAK,mBAAmB,mBAAmB,CAAC,CAAA;EAChE,OAAO,QAAQ,MAAM,aAAa,YAAY,IAAI;CACpD;CAGA,MAAM,QAAK;EACT,OAAO,MAAM,KAAK,WAAW,mBAAmB,CAAC,CAAA,EAAA,CAAI,MAAK;CAC5D;CAGA,MAAM,aAAU;EACd,OAAO,MAAM,KAAK,KAAI,EAAA,CAAI,SAAS,QAAQ,MAAM;CACnD;CAGA,MAAM,YAAS;EACb,OAAQ,MAAM,KAAK,mBAAmB,oBAAoB,CAAC,CAAA,MAAQ;CACrE;CAGA,MAAM,UAAO;EACX,OAAO,MAAM,KAAK,OAAO,SAAS,EAAA,CAAG,MAAK;CAC5C;CAGA,MAAM,SAAM;EACV,OAAO,MAAM,KAAK,OAAO,QAAQ,EAAA,CAAG,MAAK;CAC3C;CAEQ,MAAM,OAAO,OAA2B;EAC9C,MAAM,SAAS,MAAM,KAAK,mBAAmB,+BAA+B,OAAO,CAAC,CAAA;EACpF,IAAI,CAAC,QACH,MAAM,IAAI,MACR,oBAAoB,MAAK,wEAAyE;EAGtG,OAAO;CACT;CAGA,MAAM,iBAAc;EAClB,QAAQ,MAAM,KAAK,KAAI,EAAA,CAAI,gBAAgB,eAAe;CAC5D"}
|