@molecule/app-forum-thread-row-angular 0.0.0-stage → 0.0.1-stage

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,507 @@
1
+ /**
2
+ * ForumThreadRow component (React parity).
3
+ *
4
+ * @module
5
+ */
6
+ import { Component, ElementRef, inject, Renderer2, ViewChild, } from '@angular/core';
7
+ import { getClassMap } from '@molecule/app-ui';
8
+ import { hasProjectedSlot, SlotGuard } from './slot-dom.js';
9
+ import * as i0 from "@angular/core";
10
+ const _c0 = ["voteControlsWrap"];
11
+ const _c1 = ["scoreWrap"];
12
+ const _c2 = ["excerptWrap"];
13
+ const _c3 = ["authorWrap"];
14
+ const _c4 = ["createdAtWrap"];
15
+ const _c5 = ["tagsWrap"];
16
+ const _c6 = [[["", "slot", "votecontrols"]], [["", "slot", "title"]], [["", "slot", "excerpt"]], [["", "slot", "author"]], [["", "slot", "createdat"]], [["", "slot", "tags"]]];
17
+ const _c7 = ["[slot=voteControls]", "[slot=title]", "[slot=excerpt]", "[slot=author]", "[slot=createdAt]", "[slot=tags]"];
18
+ function ForumThreadRow_Conditional_12_Template(rf, ctx) { if (rf & 1) {
19
+ i0.ɵɵdomElementStart(0, "span", 6);
20
+ i0.ɵɵtext(1, "\uD83D\uDCCC");
21
+ i0.ɵɵdomElementEnd();
22
+ } }
23
+ function ForumThreadRow_Conditional_13_Template(rf, ctx) { if (rf & 1) {
24
+ i0.ɵɵdomElementStart(0, "span", 7);
25
+ i0.ɵɵtext(1, "\uD83D\uDD12");
26
+ i0.ɵɵdomElementEnd();
27
+ } }
28
+ function ForumThreadRow_Conditional_30_Template(rf, ctx) { if (rf & 1) {
29
+ i0.ɵɵdomElementStart(0, "span");
30
+ i0.ɵɵtext(1);
31
+ i0.ɵɵdomElementEnd();
32
+ } if (rf & 2) {
33
+ const ctx_r0 = i0.ɵɵnextContext();
34
+ i0.ɵɵclassMap(ctx_r0.countClasses());
35
+ i0.ɵɵadvance();
36
+ i0.ɵɵtextInterpolate1("\u00B7 ", ctx_r0.replyCount, " replies");
37
+ } }
38
+ function ForumThreadRow_Conditional_31_Template(rf, ctx) { if (rf & 1) {
39
+ i0.ɵɵdomElementStart(0, "span");
40
+ i0.ɵɵtext(1);
41
+ i0.ɵɵdomElementEnd();
42
+ } if (rf & 2) {
43
+ const ctx_r0 = i0.ɵɵnextContext();
44
+ i0.ɵɵclassMap(ctx_r0.countClasses());
45
+ i0.ɵɵadvance();
46
+ i0.ɵɵtextInterpolate1("\u00B7 ", ctx_r0.viewCount, " views");
47
+ } }
48
+ /**
49
+ * ForumThreadRow component.
50
+ *
51
+ * Forum / discussion-board thread row. Shows the title, optional excerpt, vote
52
+ * score + reply/view counts, pin/lock badges, author, and timestamp. The host
53
+ * IS the root `<article>` (`article[mol-forum-thread-row]`), matching React's
54
+ * root exactly. The counter labels ("votes", "replies", "views") and the
55
+ * pin/lock aria-labels are the same hardcoded English React renders (React has
56
+ * no companion locale bond yet).
57
+ *
58
+ * The node props are projected slots: `title`, `excerpt`, `author`,
59
+ * `createdAt`, `tags` and `voteControls` each render inside the wrapper
60
+ * element React renders for them. String inputs render as text instead —
61
+ * React's truthiness branch applies: an empty string renders nothing. The
62
+ * capture wrappers reconcile against that in {@link ForumThreadRow.ngDoCheck}.
63
+ */
64
+ export class ForumThreadRow {
65
+ /** Thread title (string/number renders as text; nodes are `[slot=title]` content). */
66
+ title;
67
+ /** Optional snippet of the OP body (string as text; nodes are `[slot=excerpt]` content). */
68
+ excerpt;
69
+ /** Aggregate vote score / upvotes (React's `!== undefined` branch). */
70
+ voteScore;
71
+ /** Reply count (React's `!== undefined` branch). */
72
+ replyCount;
73
+ /** View count (React's `!== undefined` branch). */
74
+ viewCount;
75
+ /** Optional pinned indicator. */
76
+ pinned;
77
+ /** Optional locked indicator. */
78
+ locked;
79
+ /** Author display (string as text; nodes are `[slot=author]` content). */
80
+ author;
81
+ /** Created-at display (string as text; nodes are `[slot=createdAt]` content). */
82
+ createdAt;
83
+ /** Tag chips (string as text; nodes are `[slot=tags]` content). */
84
+ tags;
85
+ /** Right-side voting controls (string as text; nodes are `[slot=voteControls]` content). */
86
+ voteControls;
87
+ /** Click handler; receives the DOM click event (React's `onClick`). */
88
+ onClick;
89
+ /** Extra classes. */
90
+ className;
91
+ /** The vote-controls wrapper `<div>` (detached when the slot is empty). */
92
+ voteControlsWrap;
93
+ /** The score `<div>` (detached when React would not render it). */
94
+ scoreWrap;
95
+ /** The excerpt `<p>` (detached when no excerpt input and the slot is empty). */
96
+ excerptWrap;
97
+ /** The author `<span>` (detached when no author input and the slot is empty). */
98
+ authorWrap;
99
+ /** The created-at `<span>` (detached when no createdAt input and the slot is empty). */
100
+ createdAtWrap;
101
+ /** The tags wrapper `<div>` (detached when no tags input and the slot is empty). */
102
+ tagsWrap;
103
+ guard = new SlotGuard(inject(ElementRef), inject(Renderer2));
104
+ /**
105
+ * The current class map.
106
+ * @returns The UIClassMap.
107
+ */
108
+ cm() {
109
+ return getClassMap();
110
+ }
111
+ /**
112
+ * Computes the row's class list (`cm.flex({ align: 'start', gap: 'sm' })` +
113
+ * `cm.sp('p', 3)` + `cm.cursorPointer` only with an `onClick` +
114
+ * `className`).
115
+ * @returns The class string.
116
+ */
117
+ classes() {
118
+ return this.cm().cn(this.cm().flex({ align: 'start', gap: 'sm' }), this.cm().sp('p', 3), this.onClick ? this.cm().cursorPointer : undefined, this.className);
119
+ }
120
+ /**
121
+ * Computes the vote-controls wrapper's class (`cm.shrink0`).
122
+ * @returns The class string.
123
+ */
124
+ voteControlsClasses() {
125
+ return this.cm().cn(this.cm().shrink0);
126
+ }
127
+ /**
128
+ * Computes the score block's class (`cm.shrink0` + `cm.textCenter` +
129
+ * `cm.stack(0)`).
130
+ * @returns The class string.
131
+ */
132
+ scoreClasses() {
133
+ return this.cm().cn(this.cm().shrink0, this.cm().textCenter, this.cm().stack(0));
134
+ }
135
+ /**
136
+ * Computes the score value's class (`cm.textSize('sm')` +
137
+ * `cm.fontWeight('bold')`).
138
+ * @returns The class string.
139
+ */
140
+ scoreValueClasses() {
141
+ return this.cm().cn(this.cm().textSize('sm'), this.cm().fontWeight('bold'));
142
+ }
143
+ /**
144
+ * Computes the "votes" label's class (`cm.textSize('xs')`).
145
+ * @returns The class string.
146
+ */
147
+ scoreLabelClasses() {
148
+ return this.cm().cn(this.cm().textSize('xs'));
149
+ }
150
+ /**
151
+ * Computes the stack's class (`cm.flex1` + `cm.stack(1)`).
152
+ * @returns The class string.
153
+ */
154
+ stackClasses() {
155
+ return this.cm().cn(this.cm().flex1, this.cm().stack(1));
156
+ }
157
+ /**
158
+ * Computes the header's class (`cm.flex({ align: 'center', gap: 'sm',
159
+ * wrap: 'wrap' })`).
160
+ * @returns The class string.
161
+ */
162
+ headerClasses() {
163
+ return this.cm().flex({ align: 'center', gap: 'sm', wrap: 'wrap' });
164
+ }
165
+ /**
166
+ * Computes the title's class (`cm.textSize('base')` +
167
+ * `cm.fontWeight('semibold')`).
168
+ * @returns The class string.
169
+ */
170
+ titleClasses() {
171
+ return this.cm().cn(this.cm().textSize('base'), this.cm().fontWeight('semibold'));
172
+ }
173
+ /**
174
+ * Computes the excerpt's class (`cm.textSize('sm')`).
175
+ * @returns The class string.
176
+ */
177
+ excerptClasses() {
178
+ return this.cm().cn(this.cm().textSize('sm'));
179
+ }
180
+ /**
181
+ * Computes the meta row's class (`cm.flex({ align: 'center', gap: 'sm',
182
+ * wrap: 'wrap' })`).
183
+ * @returns The class string.
184
+ */
185
+ metaClasses() {
186
+ return this.cm().flex({ align: 'center', gap: 'sm', wrap: 'wrap' });
187
+ }
188
+ /**
189
+ * Computes the author span's class (`cm.textSize('xs')` +
190
+ * `cm.fontWeight('semibold')`).
191
+ * @returns The class string.
192
+ */
193
+ authorClasses() {
194
+ return this.cm().cn(this.cm().textSize('xs'), this.cm().fontWeight('semibold'));
195
+ }
196
+ /**
197
+ * Computes the created-at span's class (`cm.textSize('xs')`).
198
+ * @returns The class string.
199
+ */
200
+ createdAtClasses() {
201
+ return this.cm().cn(this.cm().textSize('xs'));
202
+ }
203
+ /**
204
+ * Computes the reply/view count spans' class (`cm.textSize('xs')`).
205
+ * @returns The class string.
206
+ */
207
+ countClasses() {
208
+ return this.cm().cn(this.cm().textSize('xs'));
209
+ }
210
+ /**
211
+ * Computes the tags row's class (`cm.flex({ align: 'center', gap: 'xs',
212
+ * wrap: 'wrap' })`).
213
+ * @returns The class string.
214
+ */
215
+ tagsClasses() {
216
+ return this.cm().flex({ align: 'center', gap: 'xs', wrap: 'wrap' });
217
+ }
218
+ /**
219
+ * The input's text (React's `{expr}` for a text child: strings and numbers
220
+ * render; a node arrives as `[slot=<name>]` content instead).
221
+ * @param value - The input value.
222
+ * @returns The text, or `''` for a node value.
223
+ */
224
+ textOf(value) {
225
+ return typeof value === 'string' || typeof value === 'number' ? String(value) : '';
226
+ }
227
+ /**
228
+ * The title input's text.
229
+ * @returns The text, or `''` for a node title.
230
+ */
231
+ titleText() {
232
+ return this.textOf(this.title);
233
+ }
234
+ /**
235
+ * The excerpt input's text.
236
+ * @returns The text, or `''` for a node value.
237
+ */
238
+ excerptText() {
239
+ return this.textOf(this.excerpt);
240
+ }
241
+ /**
242
+ * The author input's text.
243
+ * @returns The text, or `''` for a node value.
244
+ */
245
+ authorText() {
246
+ return this.textOf(this.author);
247
+ }
248
+ /**
249
+ * The createdAt input's text.
250
+ * @returns The text, or `''` for a node value.
251
+ */
252
+ createdAtText() {
253
+ return this.textOf(this.createdAt);
254
+ }
255
+ /**
256
+ * The tags input's text.
257
+ * @returns The text, or `''` for a node value.
258
+ */
259
+ tagsText() {
260
+ return this.textOf(this.tags);
261
+ }
262
+ /**
263
+ * The voteControls input's text.
264
+ * @returns The text, or `''` for a node value.
265
+ */
266
+ voteControlsText() {
267
+ return this.textOf(this.voteControls);
268
+ }
269
+ /**
270
+ * Whether React would render the vote-controls wrapper: a text value or
271
+ * projected slot content.
272
+ * @returns True when vote controls are present.
273
+ */
274
+ hasVoteControls() {
275
+ return this.voteControlsText() !== '' || hasProjectedSlot(this.voteControlsWrap?.nativeElement);
276
+ }
277
+ /** Runs the click handler the way React forwards the DOM event. */
278
+ handleClick(event) {
279
+ this.onClick?.(event);
280
+ }
281
+ /**
282
+ * Reconciles the wrappers once the view queries resolve.
283
+ */
284
+ ngAfterViewInit() {
285
+ this.reconcile();
286
+ }
287
+ /**
288
+ * Re-reconciles on later passes (a props step may add an excerpt or an
289
+ * author, or empty a previously set value, which React re-renders).
290
+ */
291
+ ngDoCheck() {
292
+ this.reconcile();
293
+ }
294
+ /**
295
+ * Applies React's conditions: the vote-controls wrapper renders when the
296
+ * string input is non-empty or the slot holds projected content; the score
297
+ * block renders when `voteScore !== undefined` and no vote controls are
298
+ * supplied; each text-node wrapper renders when its value is truthy or the
299
+ * slot holds projected content. Re-read every pass, so a props step that
300
+ * empties a previously set value drops the wrapper.
301
+ */
302
+ reconcile() {
303
+ const hasVoteControls = this.hasVoteControls();
304
+ this.guard.toggle(this.voteControlsWrap?.nativeElement, hasVoteControls);
305
+ this.guard.toggle(this.scoreWrap?.nativeElement, this.voteScore !== undefined && !hasVoteControls);
306
+ const excerpt = this.excerptWrap?.nativeElement;
307
+ const author = this.authorWrap?.nativeElement;
308
+ const createdAt = this.createdAtWrap?.nativeElement;
309
+ const tags = this.tagsWrap?.nativeElement;
310
+ this.guard.toggle(excerpt, this.excerptText() !== '' || hasProjectedSlot(excerpt));
311
+ this.guard.toggle(author, this.authorText() !== '' || hasProjectedSlot(author));
312
+ this.guard.toggle(createdAt, this.createdAtText() !== '' || hasProjectedSlot(createdAt));
313
+ this.guard.toggle(tags, this.tagsText() !== '' || hasProjectedSlot(tags));
314
+ }
315
+ static ɵfac = function ForumThreadRow_Factory(__ngFactoryType__) { return new (__ngFactoryType__ || ForumThreadRow)(); };
316
+ static ɵcmp = /*@__PURE__*/ i0.ɵɵdefineComponent({ type: ForumThreadRow, selectors: [["article", "mol-forum-thread-row", ""]], viewQuery: function ForumThreadRow_Query(rf, ctx) { if (rf & 1) {
317
+ i0.ɵɵviewQuery(_c0, 5)(_c1, 5)(_c2, 5)(_c3, 5)(_c4, 5)(_c5, 5);
318
+ } if (rf & 2) {
319
+ let _t;
320
+ i0.ɵɵqueryRefresh(_t = i0.ɵɵloadQuery()) && (ctx.voteControlsWrap = _t.first);
321
+ i0.ɵɵqueryRefresh(_t = i0.ɵɵloadQuery()) && (ctx.scoreWrap = _t.first);
322
+ i0.ɵɵqueryRefresh(_t = i0.ɵɵloadQuery()) && (ctx.excerptWrap = _t.first);
323
+ i0.ɵɵqueryRefresh(_t = i0.ɵɵloadQuery()) && (ctx.authorWrap = _t.first);
324
+ i0.ɵɵqueryRefresh(_t = i0.ɵɵloadQuery()) && (ctx.createdAtWrap = _t.first);
325
+ i0.ɵɵqueryRefresh(_t = i0.ɵɵloadQuery()) && (ctx.tagsWrap = _t.first);
326
+ } }, hostVars: 2, hostBindings: function ForumThreadRow_HostBindings(rf, ctx) { if (rf & 1) {
327
+ i0.ɵɵlistener("click", function ForumThreadRow_click_HostBindingHandler($event) { return ctx.handleClick($event); });
328
+ } if (rf & 2) {
329
+ i0.ɵɵclassMap(ctx.classes());
330
+ } }, inputs: { title: "title", excerpt: "excerpt", voteScore: "voteScore", replyCount: "replyCount", viewCount: "viewCount", pinned: "pinned", locked: "locked", author: "author", createdAt: "createdAt", tags: "tags", voteControls: "voteControls", onClick: "onClick", className: "className" }, ngContentSelectors: _c7, decls: 36, vars: 35, consts: [["voteControlsWrap", ""], ["scoreWrap", ""], ["excerptWrap", ""], ["authorWrap", ""], ["createdAtWrap", ""], ["tagsWrap", ""], ["aria-label", "Pinned", "title", "Pinned"], ["aria-label", "Locked", "title", "Locked"], [3, "class"]], template: function ForumThreadRow_Template(rf, ctx) { if (rf & 1) {
331
+ i0.ɵɵprojectionDef(_c6);
332
+ i0.ɵɵdomElementStart(0, "div", null, 0);
333
+ i0.ɵɵtext(2);
334
+ i0.ɵɵprojection(3);
335
+ i0.ɵɵdomElementEnd();
336
+ i0.ɵɵdomElementStart(4, "div", null, 1)(6, "span");
337
+ i0.ɵɵtext(7);
338
+ i0.ɵɵdomElementEnd();
339
+ i0.ɵɵdomElementStart(8, "span");
340
+ i0.ɵɵtext(9, "votes");
341
+ i0.ɵɵdomElementEnd()();
342
+ i0.ɵɵdomElementStart(10, "div")(11, "header");
343
+ i0.ɵɵconditionalCreate(12, ForumThreadRow_Conditional_12_Template, 2, 0, "span", 6);
344
+ i0.ɵɵconditionalCreate(13, ForumThreadRow_Conditional_13_Template, 2, 0, "span", 7);
345
+ i0.ɵɵdomElementStart(14, "h3");
346
+ i0.ɵɵtext(15);
347
+ i0.ɵɵprojection(16, 1);
348
+ i0.ɵɵdomElementEnd()();
349
+ i0.ɵɵdomElementStart(17, "p", null, 2);
350
+ i0.ɵɵtext(19);
351
+ i0.ɵɵprojection(20, 2);
352
+ i0.ɵɵdomElementEnd();
353
+ i0.ɵɵdomElementStart(21, "div")(22, "span", null, 3);
354
+ i0.ɵɵtext(24);
355
+ i0.ɵɵprojection(25, 3);
356
+ i0.ɵɵdomElementEnd();
357
+ i0.ɵɵdomElementStart(26, "span", null, 4);
358
+ i0.ɵɵtext(28);
359
+ i0.ɵɵprojection(29, 4);
360
+ i0.ɵɵdomElementEnd();
361
+ i0.ɵɵconditionalCreate(30, ForumThreadRow_Conditional_30_Template, 2, 3, "span", 8);
362
+ i0.ɵɵconditionalCreate(31, ForumThreadRow_Conditional_31_Template, 2, 3, "span", 8);
363
+ i0.ɵɵdomElementEnd();
364
+ i0.ɵɵdomElementStart(32, "div", null, 5);
365
+ i0.ɵɵtext(34);
366
+ i0.ɵɵprojection(35, 5);
367
+ i0.ɵɵdomElementEnd()();
368
+ } if (rf & 2) {
369
+ i0.ɵɵclassMap(ctx.voteControlsClasses());
370
+ i0.ɵɵadvance(2);
371
+ i0.ɵɵtextInterpolate1(" ", ctx.voteControlsText(), " ");
372
+ i0.ɵɵadvance(2);
373
+ i0.ɵɵclassMap(ctx.scoreClasses());
374
+ i0.ɵɵadvance(2);
375
+ i0.ɵɵclassMap(ctx.scoreValueClasses());
376
+ i0.ɵɵadvance();
377
+ i0.ɵɵtextInterpolate(ctx.voteScore);
378
+ i0.ɵɵadvance();
379
+ i0.ɵɵclassMap(ctx.scoreLabelClasses());
380
+ i0.ɵɵadvance(2);
381
+ i0.ɵɵclassMap(ctx.stackClasses());
382
+ i0.ɵɵadvance();
383
+ i0.ɵɵclassMap(ctx.headerClasses());
384
+ i0.ɵɵadvance();
385
+ i0.ɵɵconditional(ctx.pinned ? 12 : -1);
386
+ i0.ɵɵadvance();
387
+ i0.ɵɵconditional(ctx.locked ? 13 : -1);
388
+ i0.ɵɵadvance();
389
+ i0.ɵɵclassMap(ctx.titleClasses());
390
+ i0.ɵɵadvance();
391
+ i0.ɵɵtextInterpolate1(" ", ctx.titleText(), " ");
392
+ i0.ɵɵadvance(2);
393
+ i0.ɵɵclassMap(ctx.excerptClasses());
394
+ i0.ɵɵadvance(2);
395
+ i0.ɵɵtextInterpolate1(" ", ctx.excerptText(), " ");
396
+ i0.ɵɵadvance(2);
397
+ i0.ɵɵclassMap(ctx.metaClasses());
398
+ i0.ɵɵadvance();
399
+ i0.ɵɵclassMap(ctx.authorClasses());
400
+ i0.ɵɵadvance(2);
401
+ i0.ɵɵtextInterpolate1(" ", ctx.authorText(), " ");
402
+ i0.ɵɵadvance(2);
403
+ i0.ɵɵclassMap(ctx.createdAtClasses());
404
+ i0.ɵɵadvance(2);
405
+ i0.ɵɵtextInterpolate1(" \u00B7 ", ctx.createdAtText(), " ");
406
+ i0.ɵɵadvance(2);
407
+ i0.ɵɵconditional(ctx.replyCount !== undefined ? 30 : -1);
408
+ i0.ɵɵadvance();
409
+ i0.ɵɵconditional(ctx.viewCount !== undefined ? 31 : -1);
410
+ i0.ɵɵadvance();
411
+ i0.ɵɵclassMap(ctx.tagsClasses());
412
+ i0.ɵɵadvance(2);
413
+ i0.ɵɵtextInterpolate1(" ", ctx.tagsText(), " ");
414
+ } }, encapsulation: 2 });
415
+ }
416
+ (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(ForumThreadRow, [{
417
+ type: Component,
418
+ args: [{
419
+ selector: 'article[mol-forum-thread-row]',
420
+ standalone: true,
421
+ inputs: [
422
+ 'title',
423
+ 'excerpt',
424
+ 'voteScore',
425
+ 'replyCount',
426
+ 'viewCount',
427
+ 'pinned',
428
+ 'locked',
429
+ 'author',
430
+ 'createdAt',
431
+ 'tags',
432
+ 'voteControls',
433
+ 'onClick',
434
+ 'className',
435
+ ],
436
+ host: {
437
+ '[class]': 'classes()',
438
+ '(click)': 'handleClick($event)',
439
+ },
440
+ template: `<div #voteControlsWrap [class]="voteControlsClasses()">
441
+ {{ voteControlsText() }}
442
+ <ng-content select="[slot=voteControls]"></ng-content>
443
+ </div>
444
+ <div #scoreWrap [class]="scoreClasses()">
445
+ <span [class]="scoreValueClasses()">{{ voteScore }}</span>
446
+ <span [class]="scoreLabelClasses()">votes</span>
447
+ </div>
448
+ <div [class]="stackClasses()">
449
+ <header [class]="headerClasses()">
450
+ @if (pinned) {
451
+ <span aria-label="Pinned" title="Pinned">📌</span>
452
+ }
453
+ @if (locked) {
454
+ <span aria-label="Locked" title="Locked">🔒</span>
455
+ }
456
+ <h3 [class]="titleClasses()">
457
+ {{ titleText() }}
458
+ <ng-content select="[slot=title]"></ng-content>
459
+ </h3>
460
+ </header>
461
+ <p #excerptWrap [class]="excerptClasses()">
462
+ {{ excerptText() }}
463
+ <ng-content select="[slot=excerpt]"></ng-content>
464
+ </p>
465
+ <div [class]="metaClasses()">
466
+ <span #authorWrap [class]="authorClasses()">
467
+ {{ authorText() }}
468
+ <ng-content select="[slot=author]"></ng-content>
469
+ </span>
470
+ <span #createdAtWrap [class]="createdAtClasses()">
471
+ · {{ createdAtText() }}
472
+ <ng-content select="[slot=createdAt]"></ng-content>
473
+ </span>
474
+ @if (replyCount !== undefined) {
475
+ <span [class]="countClasses()">· {{ replyCount }} replies</span>
476
+ }
477
+ @if (viewCount !== undefined) {
478
+ <span [class]="countClasses()">· {{ viewCount }} views</span>
479
+ }
480
+ </div>
481
+ <div #tagsWrap [class]="tagsClasses()">
482
+ {{ tagsText() }}
483
+ <ng-content select="[slot=tags]"></ng-content>
484
+ </div>
485
+ </div>`,
486
+ }]
487
+ }], null, { voteControlsWrap: [{
488
+ type: ViewChild,
489
+ args: ['voteControlsWrap']
490
+ }], scoreWrap: [{
491
+ type: ViewChild,
492
+ args: ['scoreWrap']
493
+ }], excerptWrap: [{
494
+ type: ViewChild,
495
+ args: ['excerptWrap']
496
+ }], authorWrap: [{
497
+ type: ViewChild,
498
+ args: ['authorWrap']
499
+ }], createdAtWrap: [{
500
+ type: ViewChild,
501
+ args: ['createdAtWrap']
502
+ }], tagsWrap: [{
503
+ type: ViewChild,
504
+ args: ['tagsWrap']
505
+ }] }); })();
506
+ (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(ForumThreadRow, { className: "ForumThreadRow", filePath: "forum-thread-row.component.ts", lineNumber: 107 }); })();
507
+ //# sourceMappingURL=forum-thread-row.component.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"forum-thread-row.component.js","sourceRoot":"","sources":["../src/forum-thread-row.component.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,61 @@
1
+ /**
2
+ * Angular forum / discussion-board thread row — the counterpart of
3
+ * `@molecule/app-forum-thread-row-react`: the same props and the same DOM for
4
+ * the same input.
5
+ *
6
+ * Exports `ForumThreadRow` — title, optional excerpt, vote score, reply/view
7
+ * counts, pin/lock badges, author, timestamp, tag chips, and an optional
8
+ * `voteControls` slot for inline up/down buttons.
9
+ *
10
+ * @example
11
+ * ```ts
12
+ * import { Component } from '@angular/core'
13
+ * import { ForumThreadRow } from '@molecule/app-forum-thread-row-angular'
14
+ *
15
+ * @Component({
16
+ * selector: 'app-thread',
17
+ * standalone: true,
18
+ * imports: [ForumThreadRow],
19
+ * template: `
20
+ * <article
21
+ * mol-forum-thread-row
22
+ * title="How do I reset my password?"
23
+ * excerpt="I tried the forgot-password link but never received an email."
24
+ * [voteScore]="42"
25
+ * [replyCount]="7"
26
+ * [viewCount]="320"
27
+ * author="alice"
28
+ * createdAt="2 hours ago"
29
+ * [onClick]="openThread"
30
+ * >
31
+ * <span slot="tags">help</span>
32
+ * </article>
33
+ * `,
34
+ * })
35
+ * export class Thread {
36
+ * openThread(): void {
37
+ * // navigate to the thread…
38
+ * }
39
+ * }
40
+ * ```
41
+ *
42
+ * @remarks
43
+ * - Styling routes through `getClassMap()` — a ClassMap bond (e.g.
44
+ * `@molecule/app-ui-tailwind`) must be wired or rendering throws.
45
+ * - KNOWN GAP (same as React): the counter labels ("votes", "replies",
46
+ * "views") and the pin/lock aria-labels are hardcoded English (there is no
47
+ * companion locale bond yet). For localized apps, pass pre-formatted content
48
+ * via `tags` / `voteControls` or wrap the row until the package routes text
49
+ * through `t()`.
50
+ * - The row has no `data-mol-id` support yet; add your own wrapper attribute
51
+ * if AI-agent/e2e selectors need to target it.
52
+ * - Node-valued props are projected content: `title`, `excerpt`, `author`,
53
+ * `createdAt`, `tags` and `voteControls` are selected by `[slot=<name>]`;
54
+ * string inputs render as text with React's truthiness branch (an empty
55
+ * string renders nothing).
56
+ *
57
+ * @module
58
+ */
59
+ export * from './forum-thread-row.component.js';
60
+ export * from './types.js';
61
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAyDG;AAEH,cAAc,iCAAiC,CAAA;AAC/C,cAAc,YAAY,CAAA"}
package/dist/index.js ADDED
@@ -0,0 +1,61 @@
1
+ /**
2
+ * Angular forum / discussion-board thread row — the counterpart of
3
+ * `@molecule/app-forum-thread-row-react`: the same props and the same DOM for
4
+ * the same input.
5
+ *
6
+ * Exports `ForumThreadRow` — title, optional excerpt, vote score, reply/view
7
+ * counts, pin/lock badges, author, timestamp, tag chips, and an optional
8
+ * `voteControls` slot for inline up/down buttons.
9
+ *
10
+ * @example
11
+ * ```ts
12
+ * import { Component } from '@angular/core'
13
+ * import { ForumThreadRow } from '@molecule/app-forum-thread-row-angular'
14
+ *
15
+ * @Component({
16
+ * selector: 'app-thread',
17
+ * standalone: true,
18
+ * imports: [ForumThreadRow],
19
+ * template: `
20
+ * <article
21
+ * mol-forum-thread-row
22
+ * title="How do I reset my password?"
23
+ * excerpt="I tried the forgot-password link but never received an email."
24
+ * [voteScore]="42"
25
+ * [replyCount]="7"
26
+ * [viewCount]="320"
27
+ * author="alice"
28
+ * createdAt="2 hours ago"
29
+ * [onClick]="openThread"
30
+ * >
31
+ * <span slot="tags">help</span>
32
+ * </article>
33
+ * `,
34
+ * })
35
+ * export class Thread {
36
+ * openThread(): void {
37
+ * // navigate to the thread…
38
+ * }
39
+ * }
40
+ * ```
41
+ *
42
+ * @remarks
43
+ * - Styling routes through `getClassMap()` — a ClassMap bond (e.g.
44
+ * `@molecule/app-ui-tailwind`) must be wired or rendering throws.
45
+ * - KNOWN GAP (same as React): the counter labels ("votes", "replies",
46
+ * "views") and the pin/lock aria-labels are hardcoded English (there is no
47
+ * companion locale bond yet). For localized apps, pass pre-formatted content
48
+ * via `tags` / `voteControls` or wrap the row until the package routes text
49
+ * through `t()`.
50
+ * - The row has no `data-mol-id` support yet; add your own wrapper attribute
51
+ * if AI-agent/e2e selectors need to target it.
52
+ * - Node-valued props are projected content: `title`, `excerpt`, `author`,
53
+ * `createdAt`, `tags` and `voteControls` are selected by `[slot=<name>]`;
54
+ * string inputs render as text with React's truthiness branch (an empty
55
+ * string renders nothing).
56
+ *
57
+ * @module
58
+ */
59
+ export * from './forum-thread-row.component.js';
60
+ export * from './types.js';
61
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAyDG;AAEH,cAAc,iCAAiC,CAAA;AAC/C,cAAc,YAAY,CAAA"}