@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,63 @@
1
+ /**
2
+ * DOM helpers for the projected-slot wrappers of {@link ForumThreadRow}.
3
+ *
4
+ * React renders node props inside conditional wrappers (an
5
+ * `{expr && <div>…}` branch). Angular projection needs always-mounted
6
+ * `<ng-content>` capture points, so the wrappers live in the template
7
+ * unconditionally and these helpers reconcile them against React's DOM: an
8
+ * empty slot's wrapper is detached from its parent (an anchor comment keeps
9
+ * its position stable for a later re-attach).
10
+ *
11
+ * @module
12
+ */
13
+ import type { ElementRef, Renderer2 } from '@angular/core';
14
+ /**
15
+ * Whether a wrapper holds projected slot content. The wrapper's own text
16
+ * branch contributes exactly one leading text node — the interpolation, which
17
+ * Angular creates even when the string input is empty — and Angular strips
18
+ * whitespace-only template text between it and the `<ng-content>` capture
19
+ * point. So any ELEMENT child, any text node after that first one, or any
20
+ * non-empty text node at all, is projected slot content. (The adapter
21
+ * projects slots with `ngProjectAs`, which writes no real `slot` attribute
22
+ * into the DOM, so an attribute query cannot see the projection.)
23
+ *
24
+ * @param el - The wrapper element.
25
+ * @returns True when at least one projected node exists.
26
+ */
27
+ export declare const hasProjectedSlot: (el: Element | undefined | null) => boolean;
28
+ /**
29
+ * Reconciles one projected-slot wrapper against what React renders: attach
30
+ * or detach the wrapper, whose CONTENTS move with it. Idempotent, and anchored
31
+ * in the wrapper's current parent — for a detached wrapper, the parent of its
32
+ * anchor comment, so a NESTED wrapper (the meta spans live in the meta `<div>`)
33
+ * returns to its own position, never to the host.
34
+ */
35
+ export declare class SlotGuard {
36
+ private readonly host;
37
+ private readonly renderer;
38
+ private readonly anchors;
39
+ /**
40
+ * Creates the guard.
41
+ *
42
+ * @param host - The component host element (last-resort anchor parent).
43
+ * @param renderer - The DOM renderer.
44
+ */
45
+ constructor(host: ElementRef<HTMLElement>, renderer: Renderer2);
46
+ /**
47
+ * Attaches or detaches the element (contents move with it).
48
+ *
49
+ * @param el - The wrapper element.
50
+ * @param shouldAttach - Whether it should be in the DOM.
51
+ */
52
+ toggle(el: Element | undefined | null, shouldAttach: boolean): void;
53
+ /**
54
+ * Returns the wrapper's anchor comment in `parent`, creating (or replacing
55
+ * a stale one) where the wrapper sits.
56
+ *
57
+ * @param parent - The anchor's parent.
58
+ * @param el - The wrapper the anchor stands for.
59
+ * @returns The connected anchor comment.
60
+ */
61
+ private anchorIn;
62
+ }
63
+ //# sourceMappingURL=slot-dom.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"slot-dom.d.ts","sourceRoot":"","sources":["../src/slot-dom.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,KAAK,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,eAAe,CAAA;AAE1D;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,gBAAgB,GAAI,IAAI,OAAO,GAAG,SAAS,GAAG,IAAI,KAAG,OASjE,CAAA;AAED;;;;;;GAMG;AACH,qBAAa,SAAS;IAUlB,OAAO,CAAC,QAAQ,CAAC,IAAI;IACrB,OAAO,CAAC,QAAQ,CAAC,QAAQ;IAV3B,OAAO,CAAC,QAAQ,CAAC,OAAO,CAA8B;IAEtD;;;;;OAKG;gBAEgB,IAAI,EAAE,UAAU,CAAC,WAAW,CAAC,EAC7B,QAAQ,EAAE,SAAS;IAGtC;;;;;OAKG;IACH,MAAM,CAAC,EAAE,EAAE,OAAO,GAAG,SAAS,GAAG,IAAI,EAAE,YAAY,EAAE,OAAO,GAAG,IAAI;IAoBnE;;;;;;;OAOG;IACH,OAAO,CAAC,QAAQ;CASjB"}
@@ -0,0 +1,109 @@
1
+ /**
2
+ * DOM helpers for the projected-slot wrappers of {@link ForumThreadRow}.
3
+ *
4
+ * React renders node props inside conditional wrappers (an
5
+ * `{expr && <div>…}` branch). Angular projection needs always-mounted
6
+ * `<ng-content>` capture points, so the wrappers live in the template
7
+ * unconditionally and these helpers reconcile them against React's DOM: an
8
+ * empty slot's wrapper is detached from its parent (an anchor comment keeps
9
+ * its position stable for a later re-attach).
10
+ *
11
+ * @module
12
+ */
13
+ /**
14
+ * Whether a wrapper holds projected slot content. The wrapper's own text
15
+ * branch contributes exactly one leading text node — the interpolation, which
16
+ * Angular creates even when the string input is empty — and Angular strips
17
+ * whitespace-only template text between it and the `<ng-content>` capture
18
+ * point. So any ELEMENT child, any text node after that first one, or any
19
+ * non-empty text node at all, is projected slot content. (The adapter
20
+ * projects slots with `ngProjectAs`, which writes no real `slot` attribute
21
+ * into the DOM, so an attribute query cannot see the projection.)
22
+ *
23
+ * @param el - The wrapper element.
24
+ * @returns True when at least one projected node exists.
25
+ */
26
+ export const hasProjectedSlot = (el) => {
27
+ if (!el)
28
+ return false;
29
+ const nodes = Array.from(el.childNodes);
30
+ for (const [index, node] of nodes.entries()) {
31
+ if (index === 0 && node.nodeType === 3)
32
+ continue;
33
+ if (node.nodeType === 1)
34
+ return true;
35
+ if (node.nodeType === 3 && (node.textContent ?? '').trim())
36
+ return true;
37
+ }
38
+ return false;
39
+ };
40
+ /**
41
+ * Reconciles one projected-slot wrapper against what React renders: attach
42
+ * or detach the wrapper, whose CONTENTS move with it. Idempotent, and anchored
43
+ * in the wrapper's current parent — for a detached wrapper, the parent of its
44
+ * anchor comment, so a NESTED wrapper (the meta spans live in the meta `<div>`)
45
+ * returns to its own position, never to the host.
46
+ */
47
+ export class SlotGuard {
48
+ host;
49
+ renderer;
50
+ anchors = new Map();
51
+ /**
52
+ * Creates the guard.
53
+ *
54
+ * @param host - The component host element (last-resort anchor parent).
55
+ * @param renderer - The DOM renderer.
56
+ */
57
+ constructor(host, renderer) {
58
+ this.host = host;
59
+ this.renderer = renderer;
60
+ }
61
+ /**
62
+ * Attaches or detaches the element (contents move with it).
63
+ *
64
+ * @param el - The wrapper element.
65
+ * @param shouldAttach - Whether it should be in the DOM.
66
+ */
67
+ toggle(el, shouldAttach) {
68
+ if (!el)
69
+ return;
70
+ const anchor = this.anchors.get(el);
71
+ const parent = el.parentElement ?? anchor?.parentNode ?? this.host.nativeElement;
72
+ if (shouldAttach && !el.isConnected) {
73
+ if (anchor?.isConnected && anchor.parentNode === parent) {
74
+ parent.insertBefore(el, anchor);
75
+ this.renderer.removeChild(parent, anchor);
76
+ this.anchors.delete(el);
77
+ }
78
+ else {
79
+ parent.appendChild(el);
80
+ }
81
+ }
82
+ else if (!shouldAttach && el.isConnected) {
83
+ // A placeholder comment keeps the re-insertion position stable; the
84
+ // conformance normalizer strips comment nodes, so it never shows.
85
+ this.anchorIn(parent, el);
86
+ this.renderer.removeChild(parent, el);
87
+ }
88
+ }
89
+ /**
90
+ * Returns the wrapper's anchor comment in `parent`, creating (or replacing
91
+ * a stale one) where the wrapper sits.
92
+ *
93
+ * @param parent - The anchor's parent.
94
+ * @param el - The wrapper the anchor stands for.
95
+ * @returns The connected anchor comment.
96
+ */
97
+ anchorIn(parent, el) {
98
+ const existing = this.anchors.get(el);
99
+ if (existing?.isConnected && existing.parentNode === parent)
100
+ return existing;
101
+ if (existing)
102
+ this.anchors.delete(el);
103
+ const created = this.renderer.createComment('slot');
104
+ parent.insertBefore(created, el.isConnected ? el : null);
105
+ this.anchors.set(el, created);
106
+ return created;
107
+ }
108
+ }
109
+ //# sourceMappingURL=slot-dom.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"slot-dom.js","sourceRoot":"","sources":["../src/slot-dom.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,43 @@
1
+ /**
2
+ * Types of `@molecule/app-forum-thread-row-angular` — the Angular counterpart
3
+ * of `@molecule/app-forum-thread-row-react`, same props and DOM.
4
+ *
5
+ * @module
6
+ */
7
+ /**
8
+ * Props for {@link ForumThreadRow} — the same props
9
+ * `@molecule/app-forum-thread-row-react` accepts. Node-valued props (`title`,
10
+ * `excerpt`, `author`, `createdAt`, `tags`, `voteControls`) are projected
11
+ * content in Angular (an input cannot carry a node): pass a string value
12
+ * through the input, and element content in the host template as
13
+ * `[slot=<name>]` content.
14
+ */
15
+ export interface ForumThreadRowProps {
16
+ /** Thread title; element content is projected as `[slot=title]` content. */
17
+ title?: unknown;
18
+ /** Optional snippet of the OP body; projected as `[slot=excerpt]` content. */
19
+ excerpt?: unknown;
20
+ /** Aggregate vote score / upvotes. */
21
+ voteScore?: number;
22
+ /** Reply count. */
23
+ replyCount?: number;
24
+ /** View count. */
25
+ viewCount?: number;
26
+ /** Optional pinned indicator. */
27
+ pinned?: boolean;
28
+ /** Optional locked indicator. */
29
+ locked?: boolean;
30
+ /** Author display; projected as `[slot=author]` content. */
31
+ author?: unknown;
32
+ /** Created-at display; projected as `[slot=createdAt]` content. */
33
+ createdAt?: unknown;
34
+ /** Tag chips; projected as `[slot=tags]` content. */
35
+ tags?: unknown;
36
+ /** Right-side voting controls; projected as `[slot=voteControls]` content. */
37
+ voteControls?: unknown;
38
+ /** Click handler; receives the DOM click event. */
39
+ onClick?: (event: Event) => void;
40
+ /** Extra classes. */
41
+ className?: string;
42
+ }
43
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH;;;;;;;GAOG;AACH,MAAM,WAAW,mBAAmB;IAClC,4EAA4E;IAC5E,KAAK,CAAC,EAAE,OAAO,CAAA;IACf,8EAA8E;IAC9E,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,sCAAsC;IACtC,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,mBAAmB;IACnB,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,kBAAkB;IAClB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,iCAAiC;IACjC,MAAM,CAAC,EAAE,OAAO,CAAA;IAChB,iCAAiC;IACjC,MAAM,CAAC,EAAE,OAAO,CAAA;IAChB,4DAA4D;IAC5D,MAAM,CAAC,EAAE,OAAO,CAAA;IAChB,mEAAmE;IACnE,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB,qDAAqD;IACrD,IAAI,CAAC,EAAE,OAAO,CAAA;IACd,8EAA8E;IAC9E,YAAY,CAAC,EAAE,OAAO,CAAA;IACtB,mDAAmD;IACnD,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAA;IAChC,qBAAqB;IACrB,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB"}
package/dist/types.js ADDED
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Types of `@molecule/app-forum-thread-row-angular` — the Angular counterpart
3
+ * of `@molecule/app-forum-thread-row-react`, same props and DOM.
4
+ *
5
+ * @module
6
+ */
7
+ export {};
8
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;;;GAKG"}
package/package.json CHANGED
@@ -1,6 +1,71 @@
1
1
  {
2
2
  "name": "@molecule/app-forum-thread-row-angular",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.0.1-stage",
4
+ "description": "Discussion / forum thread row with vote score, reply count, pin/lock badges, tags",
5
+ "homepage": "https://www.molecule.dev/packages/app-forum-thread-row-angular",
6
+ "type": "module",
7
+ "main": "dist/index.js",
8
+ "types": "dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "types": "./dist/index.d.ts",
12
+ "import": "./dist/index.js"
13
+ }
14
+ },
15
+ "files": [
16
+ "dist",
17
+ "README.md"
18
+ ],
19
+ "scripts": {
20
+ "build": "ngc",
21
+ "dev": "ngc --watch",
22
+ "test": "vitest run",
23
+ "test:watch": "vitest"
24
+ },
25
+ "keywords": [
26
+ "molecule",
27
+ "forum-thread-row",
28
+ "angular"
29
+ ],
30
+ "author": "Molecule Dev, Inc. (https://molecule.dev)",
31
+ "license": "Apache-2.0",
32
+ "repository": {
33
+ "type": "git",
34
+ "url": "https://github.com/molecule-dev/molecule.git",
35
+ "directory": "packages/app/features/forum-thread-row-angular"
36
+ },
37
+ "engines": {
38
+ "node": ">=20"
39
+ },
40
+ "bugs": "https://github.com/molecule-dev/molecule/issues",
41
+ "publishConfig": {
42
+ "access": "public"
43
+ },
44
+ "peerDependencies": {
45
+ "@angular/common": "^22.0.0",
46
+ "@angular/core": "^22.0.0",
47
+ "@angular/platform-browser": "^22.0.0",
48
+ "@molecule/app-angular": "^3.5.0",
49
+ "@molecule/app-ui": "^1.0.1",
50
+ "@molecule/app-ui-angular": "^1.1.0"
51
+ },
52
+ "devDependencies": {
53
+ "@angular/common": "22.2.3",
54
+ "@angular/compiler": "22.2.3",
55
+ "@angular/compiler-cli": "22.2.3",
56
+ "@angular/core": "22.2.3",
57
+ "@angular/platform-browser": "22.2.3",
58
+ "@molecule/app-angular": "3.6.0",
59
+ "@molecule/app-framework-conformance": "0.0.0",
60
+ "@molecule/app-i18n": "1.2.1",
61
+ "@molecule/app-icons": "1.0.4",
62
+ "@molecule/app-ui": "1.3.11",
63
+ "@molecule/app-ui-angular": "1.1.2",
64
+ "@types/node": "26.6.5",
65
+ "jsdom": "30.1.3",
66
+ "rxjs": "7.8.2",
67
+ "typescript": "6.0.3",
68
+ "vitest": "4.1.12",
69
+ "zone.js": "0.16.3"
70
+ }
71
+ }