@tailng-ui/primitives 0.14.0 → 0.15.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.
Files changed (52) hide show
  1. package/package.json +3 -2
  2. package/src/index.d.ts +1 -0
  3. package/src/index.d.ts.map +1 -1
  4. package/src/index.js +1 -0
  5. package/src/index.js.map +1 -1
  6. package/src/lib/form/datepicker/__tests__/tng-datepicker.test-helpers.d.ts +25 -0
  7. package/src/lib/form/datepicker/__tests__/tng-datepicker.test-helpers.d.ts.map +1 -0
  8. package/src/lib/form/datepicker/__tests__/tng-datepicker.test-helpers.js +105 -0
  9. package/src/lib/form/datepicker/__tests__/tng-datepicker.test-helpers.js.map +1 -0
  10. package/src/lib/form/datepicker/datepicker.adapters.d.ts +5 -0
  11. package/src/lib/form/datepicker/datepicker.adapters.d.ts.map +1 -0
  12. package/src/lib/form/datepicker/datepicker.adapters.js +194 -0
  13. package/src/lib/form/datepicker/datepicker.adapters.js.map +1 -0
  14. package/src/lib/form/datepicker/datepicker.state.d.ts +10 -0
  15. package/src/lib/form/datepicker/datepicker.state.d.ts.map +1 -0
  16. package/src/lib/form/datepicker/datepicker.state.js +97 -0
  17. package/src/lib/form/datepicker/datepicker.state.js.map +1 -0
  18. package/src/lib/form/datepicker/datepicker.types.d.ts +269 -0
  19. package/src/lib/form/datepicker/datepicker.types.d.ts.map +1 -0
  20. package/src/lib/form/datepicker/datepicker.types.js +1 -0
  21. package/src/lib/form/datepicker/datepicker.types.js.map +1 -0
  22. package/src/lib/form/datepicker/datepicker.utils.d.ts +75 -0
  23. package/src/lib/form/datepicker/datepicker.utils.d.ts.map +1 -0
  24. package/src/lib/form/datepicker/datepicker.utils.js +238 -0
  25. package/src/lib/form/datepicker/datepicker.utils.js.map +1 -0
  26. package/src/lib/form/datepicker/index.d.ts +3 -0
  27. package/src/lib/form/datepicker/index.d.ts.map +1 -0
  28. package/src/lib/form/datepicker/index.js +3 -0
  29. package/src/lib/form/datepicker/index.js.map +1 -0
  30. package/src/lib/form/datepicker/tng-datepicker.d.ts +5 -0
  31. package/src/lib/form/datepicker/tng-datepicker.d.ts.map +1 -0
  32. package/src/lib/form/datepicker/tng-datepicker.js +1948 -0
  33. package/src/lib/form/datepicker/tng-datepicker.js.map +1 -0
  34. package/src/lib/form/datepicker/tng-datepicker.overlay.d.ts +60 -0
  35. package/src/lib/form/datepicker/tng-datepicker.overlay.d.ts.map +1 -0
  36. package/src/lib/form/datepicker/tng-datepicker.overlay.js +275 -0
  37. package/src/lib/form/datepicker/tng-datepicker.overlay.js.map +1 -0
  38. package/src/lib/layout/grid/__tests__/tng-grid.test-harness.d.ts +32 -0
  39. package/src/lib/layout/grid/__tests__/tng-grid.test-harness.d.ts.map +1 -0
  40. package/src/lib/layout/grid/__tests__/tng-grid.test-harness.js +312 -0
  41. package/src/lib/layout/grid/__tests__/tng-grid.test-harness.js.map +1 -0
  42. package/src/lib/layout/grid/tng-grid.d.ts +144 -2
  43. package/src/lib/layout/grid/tng-grid.d.ts.map +1 -1
  44. package/src/lib/layout/grid/tng-grid.js +669 -2
  45. package/src/lib/layout/grid/tng-grid.js.map +1 -1
  46. package/src/lib/layout/tree/__tests__/tng-tree.test-harness.d.ts.map +1 -1
  47. package/src/lib/layout/tree/__tests__/tng-tree.test-harness.js +0 -1
  48. package/src/lib/layout/tree/__tests__/tng-tree.test-harness.js.map +1 -1
  49. package/src/lib/navigation/tabs/tng-tabs.d.ts +13 -6
  50. package/src/lib/navigation/tabs/tng-tabs.d.ts.map +1 -1
  51. package/src/lib/navigation/tabs/tng-tabs.js +77 -35
  52. package/src/lib/navigation/tabs/tng-tabs.js.map +1 -1
@@ -0,0 +1,275 @@
1
+ import { DestroyRef, Directive, ElementRef, HostBinding, effect, inject, input, signal, } from '@angular/core';
2
+ import { computeOverlayPosition, } from '@tailng-ui/cdk';
3
+ import * as i0 from "@angular/core";
4
+ const OVERLAY_VIEWPORT_MARGIN = 12;
5
+ const OVERLAY_OFFSET = 9;
6
+ function rectFromClientRect(rect) {
7
+ return {
8
+ height: rect.height,
9
+ left: rect.left,
10
+ top: rect.top,
11
+ width: rect.width,
12
+ };
13
+ }
14
+ function viewportRect(windowRef) {
15
+ return {
16
+ height: windowRef.innerHeight || 768,
17
+ left: 0,
18
+ top: 0,
19
+ width: windowRef.innerWidth || 1024,
20
+ };
21
+ }
22
+ function resolveAnchorElement(anchor) {
23
+ if (anchor instanceof ElementRef) {
24
+ return anchor.nativeElement;
25
+ }
26
+ return anchor instanceof HTMLElement ? anchor : null;
27
+ }
28
+ export class TngDatepickerOverlay {
29
+ elRef = inject((ElementRef));
30
+ destroyRef = inject(DestroyRef);
31
+ ownerDocument = this.elRef.nativeElement.ownerDocument ?? null;
32
+ ownerWindow = this.ownerDocument?.defaultView ?? null;
33
+ renderVersion = signal(0, ...(ngDevMode ? [{ debugName: "renderVersion" }] : []));
34
+ resolvedPlacement = signal('bottom', ...(ngDevMode ? [{ debugName: "resolvedPlacement" }] : []));
35
+ overlayPlaceholder = null;
36
+ overlayOriginalParent = null;
37
+ overlayLayoutFrame = null;
38
+ removeResizeListener = null;
39
+ removeScrollListener = null;
40
+ resizeObserver = null;
41
+ controller = input.required({ ...(ngDevMode ? { debugName: "controller" } : {}), alias: 'tngDatepickerOverlay' });
42
+ anchor = input(undefined, { ...(ngDevMode ? { debugName: "anchor" } : {}), alias: 'tngDatepickerOverlayAnchor' });
43
+ placement = input(undefined, { ...(ngDevMode ? { debugName: "placement" } : {}), alias: 'tngDatepickerOverlayPlacement' });
44
+ offset = input(undefined, { ...(ngDevMode ? { debugName: "offset" } : {}), alias: 'tngDatepickerOverlayOffset' });
45
+ collision = input(undefined, { ...(ngDevMode ? { debugName: "collision" } : {}), alias: 'tngDatepickerOverlayCollision' });
46
+ get hidden() {
47
+ this.renderVersion();
48
+ return this.controller().getOutputs().open ? null : '';
49
+ }
50
+ get display() {
51
+ this.renderVersion();
52
+ return this.controller().getOutputs().open ? null : 'none';
53
+ }
54
+ get dataPlacement() {
55
+ this.renderVersion();
56
+ return this.resolvedPlacement();
57
+ }
58
+ constructor() {
59
+ this.initializeOverlayPortal();
60
+ effect((onCleanup) => {
61
+ const controller = this.controller();
62
+ controller.registerOverlay(this.elRef.nativeElement);
63
+ const unsubscribe = controller.subscribe(() => {
64
+ this.renderVersion.update((value) => value + 1);
65
+ });
66
+ onCleanup(() => {
67
+ unsubscribe();
68
+ controller.registerOverlay(null);
69
+ });
70
+ });
71
+ effect(() => {
72
+ const open = this.controller().getOutputs().open;
73
+ this.renderVersion();
74
+ this.placement();
75
+ this.offset();
76
+ this.collision();
77
+ this.anchor();
78
+ if (open) {
79
+ this.mountToBodyAndPosition();
80
+ return;
81
+ }
82
+ this.restoreToPlaceholder();
83
+ });
84
+ this.destroyRef.onDestroy(() => {
85
+ if (this.overlayLayoutFrame !== null && this.ownerWindow !== null) {
86
+ this.ownerWindow.cancelAnimationFrame(this.overlayLayoutFrame);
87
+ this.overlayLayoutFrame = null;
88
+ }
89
+ this.teardownRepositionListeners();
90
+ this.restoreToPlaceholder(true);
91
+ });
92
+ }
93
+ initializeOverlayPortal() {
94
+ if (this.overlayPlaceholder !== null) {
95
+ return;
96
+ }
97
+ const placeholderDocument = this.ownerDocument ?? document;
98
+ const overlay = this.elRef.nativeElement;
99
+ this.overlayPlaceholder = placeholderDocument.createComment('tng-datepicker-overlay-anchor');
100
+ this.overlayOriginalParent = overlay.parentNode;
101
+ const placeholder = this.overlayPlaceholder;
102
+ if (this.overlayOriginalParent !== null && placeholder !== null) {
103
+ this.overlayOriginalParent.insertBefore(placeholder, overlay);
104
+ }
105
+ }
106
+ findAnchorEl() {
107
+ const explicitAnchor = resolveAnchorElement(this.anchor());
108
+ if (explicitAnchor !== null) {
109
+ return explicitAnchor;
110
+ }
111
+ const scope = this.overlayPlaceholder?.parentNode instanceof HTMLElement
112
+ ? this.overlayPlaceholder.parentNode
113
+ : this.overlayOriginalParent instanceof HTMLElement
114
+ ? this.overlayOriginalParent
115
+ : null;
116
+ return (scope?.querySelector('[data-slot="datepicker-input-shell"]') ??
117
+ scope?.querySelector('[data-slot="datepicker-trigger"]'));
118
+ }
119
+ scheduleReposition() {
120
+ if (!this.controller().getOutputs().open || this.ownerWindow === null) {
121
+ return;
122
+ }
123
+ if (this.overlayLayoutFrame !== null) {
124
+ this.ownerWindow.cancelAnimationFrame(this.overlayLayoutFrame);
125
+ }
126
+ this.overlayLayoutFrame = this.ownerWindow.requestAnimationFrame(() => {
127
+ this.overlayLayoutFrame = null;
128
+ this.positionOverlay();
129
+ });
130
+ }
131
+ positionOverlay() {
132
+ const overlay = this.elRef.nativeElement;
133
+ const anchor = this.findAnchorEl();
134
+ if (anchor === null || this.ownerWindow === null) {
135
+ return;
136
+ }
137
+ const anchorRect = rectFromClientRect(anchor.getBoundingClientRect());
138
+ const viewport = viewportRect(this.ownerWindow);
139
+ const width = Math.max(0, Math.min(anchorRect.width, viewport.width - OVERLAY_VIEWPORT_MARGIN * 2));
140
+ overlay.style.width = `${width}px`;
141
+ overlay.style.maxWidth = `${Math.max(0, viewport.width - OVERLAY_VIEWPORT_MARGIN * 2)}px`;
142
+ overlay.style.maxHeight = '';
143
+ const overlayRect = rectFromClientRect(overlay.getBoundingClientRect());
144
+ const result = computeOverlayPosition({
145
+ anchorRect,
146
+ collision: this.resolveCollision(),
147
+ direction: this.resolveDirection(),
148
+ offset: this.resolveOffset(),
149
+ overlayRect,
150
+ placement: this.resolvePlacement(),
151
+ viewportRect: viewport,
152
+ });
153
+ overlay.style.left = `${result.x}px`;
154
+ overlay.style.top = `${result.y}px`;
155
+ const anchorBottom = anchorRect.top + anchorRect.height;
156
+ const availableHeight = result.side === 'top'
157
+ ? Math.max(0, Math.floor(anchorRect.top - OVERLAY_VIEWPORT_MARGIN - OVERLAY_OFFSET))
158
+ : Math.max(0, Math.floor(viewport.height - anchorBottom - OVERLAY_VIEWPORT_MARGIN - OVERLAY_OFFSET));
159
+ if (availableHeight > 0) {
160
+ overlay.style.maxHeight = `${availableHeight}px`;
161
+ }
162
+ this.resolvedPlacement.set(result.side === 'top' ? 'top' : 'bottom');
163
+ overlay.style.visibility = '';
164
+ }
165
+ setupRepositionListeners() {
166
+ if (this.ownerWindow === null || this.removeResizeListener !== null || this.removeScrollListener !== null) {
167
+ return;
168
+ }
169
+ const schedule = () => {
170
+ this.scheduleReposition();
171
+ };
172
+ this.ownerWindow.addEventListener('resize', schedule);
173
+ this.ownerWindow.addEventListener('scroll', schedule, true);
174
+ this.removeResizeListener = () => this.ownerWindow?.removeEventListener('resize', schedule);
175
+ this.removeScrollListener = () => this.ownerWindow?.removeEventListener('scroll', schedule, true);
176
+ if ('ResizeObserver' in this.ownerWindow) {
177
+ const ResizeObserverCtor = this.ownerWindow.ResizeObserver;
178
+ this.resizeObserver = new ResizeObserverCtor(() => {
179
+ this.scheduleReposition();
180
+ });
181
+ const anchor = this.findAnchorEl();
182
+ if (anchor !== null && this.resizeObserver !== null) {
183
+ this.resizeObserver.observe(anchor);
184
+ }
185
+ this.resizeObserver?.observe(this.elRef.nativeElement);
186
+ }
187
+ }
188
+ teardownRepositionListeners() {
189
+ this.removeResizeListener?.();
190
+ this.removeScrollListener?.();
191
+ this.removeResizeListener = null;
192
+ this.removeScrollListener = null;
193
+ this.resizeObserver?.disconnect();
194
+ this.resizeObserver = null;
195
+ }
196
+ mountToBodyAndPosition() {
197
+ const overlay = this.elRef.nativeElement;
198
+ if (this.ownerDocument === null) {
199
+ return;
200
+ }
201
+ this.setupRepositionListeners();
202
+ if (overlay.parentNode !== this.ownerDocument.body) {
203
+ this.ownerDocument.body.appendChild(overlay);
204
+ }
205
+ overlay.style.position = 'fixed';
206
+ overlay.style.left = '0px';
207
+ overlay.style.top = '0px';
208
+ overlay.style.visibility = 'hidden';
209
+ overlay.style.zIndex = '1000';
210
+ queueMicrotask(() => {
211
+ if (!this.controller().getOutputs().open) {
212
+ return;
213
+ }
214
+ this.positionOverlay();
215
+ });
216
+ }
217
+ restoreToPlaceholder(force = false) {
218
+ const overlay = this.elRef.nativeElement;
219
+ if (!force && overlay.parentNode !== this.ownerDocument?.body) {
220
+ return;
221
+ }
222
+ const placeholder = this.overlayPlaceholder;
223
+ if (placeholder?.parentNode !== null && placeholder !== null) {
224
+ placeholder.parentNode.insertBefore(overlay, placeholder);
225
+ }
226
+ else if (this.overlayOriginalParent !== null) {
227
+ this.overlayOriginalParent.appendChild(overlay);
228
+ }
229
+ this.teardownRepositionListeners();
230
+ this.resolvedPlacement.set(this.resolvePlacement().side === 'top' ? 'top' : 'bottom');
231
+ overlay.style.left = '';
232
+ overlay.style.maxHeight = '';
233
+ overlay.style.maxWidth = '';
234
+ overlay.style.position = '';
235
+ overlay.style.top = '';
236
+ overlay.style.visibility = '';
237
+ overlay.style.width = '';
238
+ overlay.style.zIndex = '';
239
+ }
240
+ resolvePlacement() {
241
+ return this.placement() ?? { align: 'start', side: 'bottom' };
242
+ }
243
+ resolveOffset() {
244
+ return this.offset() ?? { side: OVERLAY_OFFSET };
245
+ }
246
+ resolveCollision() {
247
+ return this.collision() ?? {
248
+ flip: true,
249
+ padding: OVERLAY_VIEWPORT_MARGIN,
250
+ shift: true,
251
+ };
252
+ }
253
+ resolveDirection() {
254
+ return this.controller().getOutputs().getHostAttributes()['dir'] === 'rtl' ? 'rtl' : 'ltr';
255
+ }
256
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.1", ngImport: i0, type: TngDatepickerOverlay, deps: [], target: i0.ɵɵFactoryTarget.Directive });
257
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.1.1", type: TngDatepickerOverlay, isStandalone: true, selector: "[tngDatepickerOverlay]", inputs: { controller: { classPropertyName: "controller", publicName: "tngDatepickerOverlay", isSignal: true, isRequired: true, transformFunction: null }, anchor: { classPropertyName: "anchor", publicName: "tngDatepickerOverlayAnchor", isSignal: true, isRequired: false, transformFunction: null }, placement: { classPropertyName: "placement", publicName: "tngDatepickerOverlayPlacement", isSignal: true, isRequired: false, transformFunction: null }, offset: { classPropertyName: "offset", publicName: "tngDatepickerOverlayOffset", isSignal: true, isRequired: false, transformFunction: null }, collision: { classPropertyName: "collision", publicName: "tngDatepickerOverlayCollision", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "attr.hidden": "this.hidden", "style.display": "this.display", "attr.data-placement": "this.dataPlacement" } }, exportAs: ["tngDatepickerOverlay"], ngImport: i0 });
258
+ }
259
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.1", ngImport: i0, type: TngDatepickerOverlay, decorators: [{
260
+ type: Directive,
261
+ args: [{
262
+ selector: '[tngDatepickerOverlay]',
263
+ exportAs: 'tngDatepickerOverlay',
264
+ }]
265
+ }], ctorParameters: () => [], propDecorators: { controller: [{ type: i0.Input, args: [{ isSignal: true, alias: "tngDatepickerOverlay", required: true }] }], anchor: [{ type: i0.Input, args: [{ isSignal: true, alias: "tngDatepickerOverlayAnchor", required: false }] }], placement: [{ type: i0.Input, args: [{ isSignal: true, alias: "tngDatepickerOverlayPlacement", required: false }] }], offset: [{ type: i0.Input, args: [{ isSignal: true, alias: "tngDatepickerOverlayOffset", required: false }] }], collision: [{ type: i0.Input, args: [{ isSignal: true, alias: "tngDatepickerOverlayCollision", required: false }] }], hidden: [{
266
+ type: HostBinding,
267
+ args: ['attr.hidden']
268
+ }], display: [{
269
+ type: HostBinding,
270
+ args: ['style.display']
271
+ }], dataPlacement: [{
272
+ type: HostBinding,
273
+ args: ['attr.data-placement']
274
+ }] } });
275
+ //# sourceMappingURL=tng-datepicker.overlay.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tng-datepicker.overlay.js","sourceRoot":"","sources":["../../../../../../../../libs/tailng-ui/primitives/src/lib/form/datepicker/tng-datepicker.overlay.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n DestroyRef,\n Directive,\n ElementRef,\n HostBinding,\n effect,\n inject,\n input,\n signal,\n} from '@angular/core';\nimport {\n computeOverlayPosition,\n type TngOverlayCollisionOptions,\n type TngOverlayOffset,\n type TngOverlayPlacement,\n} from '@tailng-ui/cdk';\nimport type {\n TngDatepickerAttributeMap,\n} from './datepicker.types';\n\ntype TngDatepickerOverlayController = Readonly<{\n getOutputs: () => Readonly<{\n getHostAttributes: () => TngDatepickerAttributeMap;\n open: boolean;\n }>;\n registerOverlay: (element: HTMLElement | null) => void;\n subscribe: (listener: (event: unknown) => void) => () => void;\n}>;\n\ntype MaybeRect = Readonly<{\n height: number;\n left: number;\n top: number;\n width: number;\n}>;\n\ntype OverlayAnchorInput =\n | ElementRef<HTMLElement>\n | HTMLElement\n | null\n | undefined;\n\nconst OVERLAY_VIEWPORT_MARGIN = 12;\nconst OVERLAY_OFFSET = 9;\n\nfunction rectFromClientRect(rect: DOMRect | ClientRect): MaybeRect {\n return {\n height: rect.height,\n left: rect.left,\n top: rect.top,\n width: rect.width,\n };\n}\n\nfunction viewportRect(windowRef: Window): MaybeRect {\n return {\n height: windowRef.innerHeight || 768,\n left: 0,\n top: 0,\n width: windowRef.innerWidth || 1024,\n };\n}\n\nfunction resolveAnchorElement(anchor: OverlayAnchorInput): HTMLElement | null {\n if (anchor instanceof ElementRef) {\n return anchor.nativeElement;\n }\n\n return anchor instanceof HTMLElement ? anchor : null;\n}\n\n@Directive({\n selector: '[tngDatepickerOverlay]',\n exportAs: 'tngDatepickerOverlay',\n})\nexport class TngDatepickerOverlay {\n private readonly elRef = inject(ElementRef<HTMLElement>);\n private readonly destroyRef = inject(DestroyRef);\n\n private readonly ownerDocument = this.elRef.nativeElement.ownerDocument ?? null;\n private readonly ownerWindow = this.ownerDocument?.defaultView ?? null;\n private readonly renderVersion = signal(0);\n private readonly resolvedPlacement = signal<'bottom' | 'top'>('bottom');\n\n private overlayPlaceholder: Comment | null = null;\n private overlayOriginalParent: Node | null = null;\n private overlayLayoutFrame: number | null = null;\n private removeResizeListener: (() => void) | null = null;\n private removeScrollListener: (() => void) | null = null;\n private resizeObserver: ResizeObserver | null = null;\n\n public readonly controller = input.required<TngDatepickerOverlayController>({\n alias: 'tngDatepickerOverlay',\n });\n public readonly anchor = input<OverlayAnchorInput>(undefined, {\n alias: 'tngDatepickerOverlayAnchor',\n });\n public readonly placement = input<TngOverlayPlacement | undefined>(undefined, {\n alias: 'tngDatepickerOverlayPlacement',\n });\n public readonly offset = input<TngOverlayOffset | undefined>(undefined, {\n alias: 'tngDatepickerOverlayOffset',\n });\n public readonly collision = input<TngOverlayCollisionOptions | undefined>(undefined, {\n alias: 'tngDatepickerOverlayCollision',\n });\n\n @HostBinding('attr.hidden')\n protected get hidden(): '' | null {\n this.renderVersion();\n return this.controller().getOutputs().open ? null : '';\n }\n\n @HostBinding('style.display')\n protected get display(): string | null {\n this.renderVersion();\n return this.controller().getOutputs().open ? null : 'none';\n }\n\n @HostBinding('attr.data-placement')\n protected get dataPlacement(): 'bottom' | 'top' {\n this.renderVersion();\n return this.resolvedPlacement();\n }\n\n public constructor() {\n this.initializeOverlayPortal();\n\n effect((onCleanup) => {\n const controller = this.controller();\n controller.registerOverlay(this.elRef.nativeElement);\n const unsubscribe = controller.subscribe(() => {\n this.renderVersion.update((value) => value + 1);\n });\n\n onCleanup(() => {\n unsubscribe();\n controller.registerOverlay(null);\n });\n });\n\n effect(() => {\n const open = this.controller().getOutputs().open;\n this.renderVersion();\n this.placement();\n this.offset();\n this.collision();\n this.anchor();\n\n if (open) {\n this.mountToBodyAndPosition();\n return;\n }\n\n this.restoreToPlaceholder();\n });\n\n this.destroyRef.onDestroy(() => {\n if (this.overlayLayoutFrame !== null && this.ownerWindow !== null) {\n this.ownerWindow.cancelAnimationFrame(this.overlayLayoutFrame);\n this.overlayLayoutFrame = null;\n }\n\n this.teardownRepositionListeners();\n this.restoreToPlaceholder(true);\n });\n }\n\n private initializeOverlayPortal(): void {\n if (this.overlayPlaceholder !== null) {\n return;\n }\n\n const placeholderDocument = this.ownerDocument ?? document;\n const overlay = this.elRef.nativeElement;\n this.overlayPlaceholder = placeholderDocument.createComment('tng-datepicker-overlay-anchor');\n this.overlayOriginalParent = overlay.parentNode;\n const placeholder = this.overlayPlaceholder;\n if (this.overlayOriginalParent !== null && placeholder !== null) {\n this.overlayOriginalParent.insertBefore(placeholder, overlay);\n }\n }\n\n private findAnchorEl(): HTMLElement | null {\n const explicitAnchor = resolveAnchorElement(this.anchor());\n if (explicitAnchor !== null) {\n return explicitAnchor;\n }\n\n const scope =\n this.overlayPlaceholder?.parentNode instanceof HTMLElement\n ? this.overlayPlaceholder.parentNode\n : this.overlayOriginalParent instanceof HTMLElement\n ? this.overlayOriginalParent\n : null;\n\n return (\n scope?.querySelector('[data-slot=\"datepicker-input-shell\"]') ??\n scope?.querySelector('[data-slot=\"datepicker-trigger\"]')\n ) as HTMLElement | null;\n }\n\n private scheduleReposition(): void {\n if (!this.controller().getOutputs().open || this.ownerWindow === null) {\n return;\n }\n\n if (this.overlayLayoutFrame !== null) {\n this.ownerWindow.cancelAnimationFrame(this.overlayLayoutFrame);\n }\n\n this.overlayLayoutFrame = this.ownerWindow.requestAnimationFrame(() => {\n this.overlayLayoutFrame = null;\n this.positionOverlay();\n });\n }\n\n private positionOverlay(): void {\n const overlay = this.elRef.nativeElement;\n const anchor = this.findAnchorEl();\n if (anchor === null || this.ownerWindow === null) {\n return;\n }\n\n const anchorRect = rectFromClientRect(anchor.getBoundingClientRect());\n const viewport = viewportRect(this.ownerWindow);\n const width = Math.max(\n 0,\n Math.min(anchorRect.width, viewport.width - OVERLAY_VIEWPORT_MARGIN * 2),\n );\n\n overlay.style.width = `${width}px`;\n overlay.style.maxWidth = `${Math.max(0, viewport.width - OVERLAY_VIEWPORT_MARGIN * 2)}px`;\n overlay.style.maxHeight = '';\n\n const overlayRect = rectFromClientRect(overlay.getBoundingClientRect());\n const result = computeOverlayPosition({\n anchorRect,\n collision: this.resolveCollision(),\n direction: this.resolveDirection(),\n offset: this.resolveOffset(),\n overlayRect,\n placement: this.resolvePlacement(),\n viewportRect: viewport,\n });\n\n overlay.style.left = `${result.x}px`;\n overlay.style.top = `${result.y}px`;\n\n const anchorBottom = anchorRect.top + anchorRect.height;\n const availableHeight =\n result.side === 'top'\n ? Math.max(0, Math.floor(anchorRect.top - OVERLAY_VIEWPORT_MARGIN - OVERLAY_OFFSET))\n : Math.max(\n 0,\n Math.floor(viewport.height - anchorBottom - OVERLAY_VIEWPORT_MARGIN - OVERLAY_OFFSET),\n );\n\n if (availableHeight > 0) {\n overlay.style.maxHeight = `${availableHeight}px`;\n }\n\n this.resolvedPlacement.set(result.side === 'top' ? 'top' : 'bottom');\n overlay.style.visibility = '';\n }\n\n private setupRepositionListeners(): void {\n if (this.ownerWindow === null || this.removeResizeListener !== null || this.removeScrollListener !== null) {\n return;\n }\n\n const schedule = (): void => {\n this.scheduleReposition();\n };\n\n this.ownerWindow.addEventListener('resize', schedule);\n this.ownerWindow.addEventListener('scroll', schedule, true);\n this.removeResizeListener = () => this.ownerWindow?.removeEventListener('resize', schedule);\n this.removeScrollListener = () => this.ownerWindow?.removeEventListener('scroll', schedule, true);\n\n if ('ResizeObserver' in this.ownerWindow) {\n const ResizeObserverCtor = this.ownerWindow.ResizeObserver;\n this.resizeObserver = new ResizeObserverCtor(() => {\n this.scheduleReposition();\n });\n\n const anchor = this.findAnchorEl();\n if (anchor !== null && this.resizeObserver !== null) {\n this.resizeObserver.observe(anchor);\n }\n\n this.resizeObserver?.observe(this.elRef.nativeElement);\n }\n }\n\n private teardownRepositionListeners(): void {\n this.removeResizeListener?.();\n this.removeScrollListener?.();\n this.removeResizeListener = null;\n this.removeScrollListener = null;\n this.resizeObserver?.disconnect();\n this.resizeObserver = null;\n }\n\n private mountToBodyAndPosition(): void {\n const overlay = this.elRef.nativeElement;\n if (this.ownerDocument === null) {\n return;\n }\n\n this.setupRepositionListeners();\n\n if (overlay.parentNode !== this.ownerDocument.body) {\n this.ownerDocument.body.appendChild(overlay);\n }\n\n overlay.style.position = 'fixed';\n overlay.style.left = '0px';\n overlay.style.top = '0px';\n overlay.style.visibility = 'hidden';\n overlay.style.zIndex = '1000';\n\n queueMicrotask(() => {\n if (!this.controller().getOutputs().open) {\n return;\n }\n\n this.positionOverlay();\n });\n }\n\n private restoreToPlaceholder(force = false): void {\n const overlay = this.elRef.nativeElement;\n if (!force && overlay.parentNode !== this.ownerDocument?.body) {\n return;\n }\n\n const placeholder = this.overlayPlaceholder;\n if (placeholder?.parentNode !== null && placeholder !== null) {\n placeholder.parentNode.insertBefore(overlay, placeholder);\n } else if (this.overlayOriginalParent !== null) {\n this.overlayOriginalParent.appendChild(overlay);\n }\n\n this.teardownRepositionListeners();\n this.resolvedPlacement.set(this.resolvePlacement().side === 'top' ? 'top' : 'bottom');\n overlay.style.left = '';\n overlay.style.maxHeight = '';\n overlay.style.maxWidth = '';\n overlay.style.position = '';\n overlay.style.top = '';\n overlay.style.visibility = '';\n overlay.style.width = '';\n overlay.style.zIndex = '';\n }\n\n private resolvePlacement(): TngOverlayPlacement {\n return this.placement() ?? { align: 'start', side: 'bottom' };\n }\n\n private resolveOffset(): TngOverlayOffset {\n return this.offset() ?? { side: OVERLAY_OFFSET };\n }\n\n private resolveCollision(): TngOverlayCollisionOptions {\n return this.collision() ?? {\n flip: true,\n padding: OVERLAY_VIEWPORT_MARGIN,\n shift: true,\n };\n }\n\n private resolveDirection(): 'ltr' | 'rtl' {\n return this.controller().getOutputs().getHostAttributes()['dir'] === 'rtl' ? 'rtl' : 'ltr';\n }\n}\n"]}
@@ -0,0 +1,32 @@
1
+ import { type ComponentFixture } from '@angular/core/testing';
2
+ import type { DebugElement } from '@angular/core';
3
+ import { type TngGridActivateEvent, type TngGridCellValue, type TngGridFocusChangeEvent } from '../tng-grid';
4
+ import * as i0 from "@angular/core";
5
+ export declare class GridHarnessComponent {
6
+ ariaLabel: string;
7
+ ariaColcount: number | null;
8
+ ariaRowcount: number | null;
9
+ dir: 'ltr' | 'rtl';
10
+ focusRow: number | null | undefined;
11
+ focusCol: number | null | undefined;
12
+ selectionMode: 'none' | 'single';
13
+ value: TngGridCellValue | null | undefined;
14
+ defaultValue: TngGridCellValue | null | undefined;
15
+ wrap: boolean;
16
+ disableCell01: boolean;
17
+ readonly focusRowChanges: Array<number | null>;
18
+ readonly focusColChanges: Array<number | null>;
19
+ readonly focusEvents: TngGridFocusChangeEvent[];
20
+ readonly valueChanges: Array<TngGridCellValue | null>;
21
+ readonly activateEvents: TngGridActivateEvent[];
22
+ static ɵfac: i0.ɵɵFactoryDeclaration<GridHarnessComponent, never>;
23
+ static ɵcmp: i0.ɵɵComponentDeclaration<GridHarnessComponent, "ng-component", never, {}, {}, never, never, true, never>;
24
+ }
25
+ export declare function createGridHarnessFixture(overrides?: Partial<GridHarnessComponent>): Promise<ComponentFixture<GridHarnessComponent>>;
26
+ export declare function dispatchGridKeydown(element: HTMLElement, key: string, init?: KeyboardEventInit): KeyboardEvent;
27
+ export declare function getGrid(fixture: ComponentFixture<GridHarnessComponent>): HTMLElement;
28
+ export declare function getOutsideButton(fixture: ComponentFixture<GridHarnessComponent>): HTMLButtonElement;
29
+ export declare function getCell(fixture: ComponentFixture<GridHarnessComponent>, row: number, col: number): HTMLButtonElement;
30
+ export declare function getRow(fixture: ComponentFixture<GridHarnessComponent>, row: number): HTMLElement;
31
+ export declare function getCellDebugElement(fixture: ComponentFixture<GridHarnessComponent>, row: number, col: number): DebugElement;
32
+ //# sourceMappingURL=tng-grid.test-harness.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tng-grid.test-harness.d.ts","sourceRoot":"","sources":["../../../../../../../../../libs/tailng-ui/primitives/src/lib/layout/grid/__tests__/tng-grid.test-harness.ts"],"names":[],"mappings":"AACA,OAAO,EAAW,KAAK,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AACvE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAElD,OAAO,EAGL,KAAK,oBAAoB,EACzB,KAAK,gBAAgB,EACrB,KAAK,uBAAuB,EAE7B,MAAM,aAAa,CAAC;;AAErB,qBAyHa,oBAAoB;IACxB,SAAS,SAAkB;IAC3B,YAAY,EAAE,MAAM,GAAG,IAAI,CAAQ;IACnC,YAAY,EAAE,MAAM,GAAG,IAAI,CAAQ;IACnC,GAAG,EAAE,KAAK,GAAG,KAAK,CAAS;IAC3B,QAAQ,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,CAAa;IAChD,QAAQ,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,CAAa;IAChD,aAAa,EAAE,MAAM,GAAG,QAAQ,CAAU;IAC1C,KAAK,EAAE,gBAAgB,GAAG,IAAI,GAAG,SAAS,CAAa;IACvD,YAAY,EAAE,gBAAgB,GAAG,IAAI,GAAG,SAAS,CAAa;IAC9D,IAAI,UAAS;IACb,aAAa,UAAQ;IAE5B,SAAgB,eAAe,EAAE,KAAK,CAAC,MAAM,GAAG,IAAI,CAAC,CAAM;IAC3D,SAAgB,eAAe,EAAE,KAAK,CAAC,MAAM,GAAG,IAAI,CAAC,CAAM;IAC3D,SAAgB,WAAW,EAAE,uBAAuB,EAAE,CAAM;IAC5D,SAAgB,YAAY,EAAE,KAAK,CAAC,gBAAgB,GAAG,IAAI,CAAC,CAAM;IAClE,SAAgB,cAAc,EAAE,oBAAoB,EAAE,CAAM;yCAjBjD,oBAAoB;2CAApB,oBAAoB;CAkBhC;AAED,wBAAsB,wBAAwB,CAC5C,SAAS,GAAE,OAAO,CAAC,oBAAoB,CAAM,GAC5C,OAAO,CAAC,gBAAgB,CAAC,oBAAoB,CAAC,CAAC,CASjD;AAED,wBAAgB,mBAAmB,CACjC,OAAO,EAAE,WAAW,EACpB,GAAG,EAAE,MAAM,EACX,IAAI,GAAE,iBAAsB,GAC3B,aAAa,CASf;AAED,wBAAgB,OAAO,CAAC,OAAO,EAAE,gBAAgB,CAAC,oBAAoB,CAAC,GAAG,WAAW,CAEpF;AAED,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,gBAAgB,CAAC,oBAAoB,CAAC,GAAG,iBAAiB,CAEnG;AAED,wBAAgB,OAAO,CACrB,OAAO,EAAE,gBAAgB,CAAC,oBAAoB,CAAC,EAC/C,GAAG,EAAE,MAAM,EACX,GAAG,EAAE,MAAM,GACV,iBAAiB,CAEnB;AAED,wBAAgB,MAAM,CACpB,OAAO,EAAE,gBAAgB,CAAC,oBAAoB,CAAC,EAC/C,GAAG,EAAE,MAAM,GACV,WAAW,CAEb;AAED,wBAAgB,mBAAmB,CACjC,OAAO,EAAE,gBAAgB,CAAC,oBAAoB,CAAC,EAC/C,GAAG,EAAE,MAAM,EACX,GAAG,EAAE,MAAM,GACV,YAAY,CAOd"}
@@ -0,0 +1,312 @@
1
+ import { Component } from '@angular/core';
2
+ import { TestBed } from '@angular/core/testing';
3
+ import { By } from '@angular/platform-browser';
4
+ import { TngGrid, TngGridCell, TngGridRow, } from '../tng-grid';
5
+ import * as i0 from "@angular/core";
6
+ export class GridHarnessComponent {
7
+ ariaLabel = 'Harness grid';
8
+ ariaColcount = null;
9
+ ariaRowcount = null;
10
+ dir = 'ltr';
11
+ focusRow = undefined;
12
+ focusCol = undefined;
13
+ selectionMode = 'none';
14
+ value = undefined;
15
+ defaultValue = undefined;
16
+ wrap = false;
17
+ disableCell01 = true;
18
+ focusRowChanges = [];
19
+ focusColChanges = [];
20
+ focusEvents = [];
21
+ valueChanges = [];
22
+ activateEvents = [];
23
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.1", ngImport: i0, type: GridHarnessComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
24
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.1.1", type: GridHarnessComponent, isStandalone: true, selector: "ng-component", ngImport: i0, template: `
25
+ <div
26
+ tngGrid
27
+ [ariaLabel]="ariaLabel"
28
+ [ariaColcount]="ariaColcount"
29
+ [ariaRowcount]="ariaRowcount"
30
+ [dir]="dir"
31
+ [focusRow]="focusRow"
32
+ [focusCol]="focusCol"
33
+ [selectionMode]="selectionMode"
34
+ [value]="value"
35
+ [defaultValue]="defaultValue"
36
+ [wrap]="wrap"
37
+ (focusRowChange)="focusRowChanges.push($event)"
38
+ (focusColChange)="focusColChanges.push($event)"
39
+ (focusChange)="focusEvents.push($event)"
40
+ (valueChange)="valueChanges.push($event)"
41
+ (cellActivate)="activateEvents.push($event)"
42
+ data-testid="grid"
43
+ >
44
+ <div tngGridRow data-testid="row-0">
45
+ <button
46
+ type="button"
47
+ tngGridCell
48
+ data-testid="cell-0-0"
49
+ cellRole="columnheader"
50
+ [rowIndex]="0"
51
+ [colIndex]="0"
52
+ >
53
+ Header
54
+ </button>
55
+ <button
56
+ type="button"
57
+ tngGridCell
58
+ data-testid="cell-0-1"
59
+ [disabled]="disableCell01"
60
+ [rowIndex]="0"
61
+ [colIndex]="1"
62
+ >
63
+ Alpha
64
+ </button>
65
+ <button
66
+ type="button"
67
+ tngGridCell
68
+ data-testid="cell-0-2"
69
+ [rowIndex]="0"
70
+ [colIndex]="2"
71
+ >
72
+ Beta
73
+ </button>
74
+ </div>
75
+
76
+ <div tngGridRow data-testid="row-1">
77
+ <button
78
+ type="button"
79
+ tngGridCell
80
+ data-testid="cell-1-0"
81
+ [rowIndex]="1"
82
+ [colIndex]="0"
83
+ >
84
+ Gamma
85
+ </button>
86
+ <button
87
+ type="button"
88
+ tngGridCell
89
+ data-testid="cell-1-1"
90
+ [rowIndex]="1"
91
+ [colIndex]="1"
92
+ [rowSpan]="2"
93
+ [colSpan]="2"
94
+ >
95
+ Delta
96
+ </button>
97
+ <button
98
+ type="button"
99
+ tngGridCell
100
+ data-testid="cell-1-2"
101
+ [rowIndex]="1"
102
+ [colIndex]="2"
103
+ >
104
+ Epsilon
105
+ </button>
106
+ </div>
107
+
108
+ <div tngGridRow data-testid="row-2">
109
+ <button
110
+ type="button"
111
+ tngGridCell
112
+ data-testid="cell-2-0"
113
+ [rowIndex]="2"
114
+ [colIndex]="0"
115
+ >
116
+ Zeta
117
+ </button>
118
+ <button
119
+ type="button"
120
+ tngGridCell
121
+ data-testid="cell-2-1"
122
+ [rowIndex]="2"
123
+ [colIndex]="1"
124
+ >
125
+ Eta
126
+ </button>
127
+ <button
128
+ type="button"
129
+ tngGridCell
130
+ data-testid="cell-2-2"
131
+ [rowIndex]="2"
132
+ [colIndex]="2"
133
+ >
134
+ Theta
135
+ </button>
136
+ </div>
137
+ </div>
138
+
139
+ <button type="button" data-testid="outside-button">Outside</button>
140
+ `, isInline: true, dependencies: [{ kind: "directive", type: TngGrid, selector: "[tngGrid]", inputs: ["dir", "focusCol", "focusRow", "selectionMode", "value", "defaultValue", "wrap", "ariaLabel", "ariaColcount", "ariaRowcount"], outputs: ["focusRowChange", "focusColChange", "focusChange", "valueChange", "cellActivate"], exportAs: ["tngGrid"] }, { kind: "directive", type: TngGridRow, selector: "[tngGridRow]", exportAs: ["tngGridRow"] }, { kind: "directive", type: TngGridCell, selector: "[tngGridCell]", inputs: ["rowIndex", "colIndex", "rowSpan", "colSpan", "disabled", "selected", "cellRole"], exportAs: ["tngGridCell"] }] });
141
+ }
142
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.1", ngImport: i0, type: GridHarnessComponent, decorators: [{
143
+ type: Component,
144
+ args: [{
145
+ standalone: true,
146
+ imports: [TngGrid, TngGridRow, TngGridCell],
147
+ template: `
148
+ <div
149
+ tngGrid
150
+ [ariaLabel]="ariaLabel"
151
+ [ariaColcount]="ariaColcount"
152
+ [ariaRowcount]="ariaRowcount"
153
+ [dir]="dir"
154
+ [focusRow]="focusRow"
155
+ [focusCol]="focusCol"
156
+ [selectionMode]="selectionMode"
157
+ [value]="value"
158
+ [defaultValue]="defaultValue"
159
+ [wrap]="wrap"
160
+ (focusRowChange)="focusRowChanges.push($event)"
161
+ (focusColChange)="focusColChanges.push($event)"
162
+ (focusChange)="focusEvents.push($event)"
163
+ (valueChange)="valueChanges.push($event)"
164
+ (cellActivate)="activateEvents.push($event)"
165
+ data-testid="grid"
166
+ >
167
+ <div tngGridRow data-testid="row-0">
168
+ <button
169
+ type="button"
170
+ tngGridCell
171
+ data-testid="cell-0-0"
172
+ cellRole="columnheader"
173
+ [rowIndex]="0"
174
+ [colIndex]="0"
175
+ >
176
+ Header
177
+ </button>
178
+ <button
179
+ type="button"
180
+ tngGridCell
181
+ data-testid="cell-0-1"
182
+ [disabled]="disableCell01"
183
+ [rowIndex]="0"
184
+ [colIndex]="1"
185
+ >
186
+ Alpha
187
+ </button>
188
+ <button
189
+ type="button"
190
+ tngGridCell
191
+ data-testid="cell-0-2"
192
+ [rowIndex]="0"
193
+ [colIndex]="2"
194
+ >
195
+ Beta
196
+ </button>
197
+ </div>
198
+
199
+ <div tngGridRow data-testid="row-1">
200
+ <button
201
+ type="button"
202
+ tngGridCell
203
+ data-testid="cell-1-0"
204
+ [rowIndex]="1"
205
+ [colIndex]="0"
206
+ >
207
+ Gamma
208
+ </button>
209
+ <button
210
+ type="button"
211
+ tngGridCell
212
+ data-testid="cell-1-1"
213
+ [rowIndex]="1"
214
+ [colIndex]="1"
215
+ [rowSpan]="2"
216
+ [colSpan]="2"
217
+ >
218
+ Delta
219
+ </button>
220
+ <button
221
+ type="button"
222
+ tngGridCell
223
+ data-testid="cell-1-2"
224
+ [rowIndex]="1"
225
+ [colIndex]="2"
226
+ >
227
+ Epsilon
228
+ </button>
229
+ </div>
230
+
231
+ <div tngGridRow data-testid="row-2">
232
+ <button
233
+ type="button"
234
+ tngGridCell
235
+ data-testid="cell-2-0"
236
+ [rowIndex]="2"
237
+ [colIndex]="0"
238
+ >
239
+ Zeta
240
+ </button>
241
+ <button
242
+ type="button"
243
+ tngGridCell
244
+ data-testid="cell-2-1"
245
+ [rowIndex]="2"
246
+ [colIndex]="1"
247
+ >
248
+ Eta
249
+ </button>
250
+ <button
251
+ type="button"
252
+ tngGridCell
253
+ data-testid="cell-2-2"
254
+ [rowIndex]="2"
255
+ [colIndex]="2"
256
+ >
257
+ Theta
258
+ </button>
259
+ </div>
260
+ </div>
261
+
262
+ <button type="button" data-testid="outside-button">Outside</button>
263
+ `,
264
+ }]
265
+ }] });
266
+ export async function createGridHarnessFixture(overrides = {}) {
267
+ await TestBed.configureTestingModule({
268
+ imports: [GridHarnessComponent],
269
+ }).compileComponents();
270
+ const fixture = TestBed.createComponent(GridHarnessComponent);
271
+ Object.assign(fixture.componentInstance, overrides);
272
+ fixture.detectChanges();
273
+ return fixture;
274
+ }
275
+ export function dispatchGridKeydown(element, key, init = {}) {
276
+ const event = new KeyboardEvent('keydown', {
277
+ bubbles: true,
278
+ cancelable: true,
279
+ key,
280
+ ...init,
281
+ });
282
+ element.dispatchEvent(event);
283
+ return event;
284
+ }
285
+ export function getGrid(fixture) {
286
+ return queryByTestId(fixture, 'grid');
287
+ }
288
+ export function getOutsideButton(fixture) {
289
+ return queryByTestId(fixture, 'outside-button');
290
+ }
291
+ export function getCell(fixture, row, col) {
292
+ return queryByTestId(fixture, `cell-${row}-${col}`);
293
+ }
294
+ export function getRow(fixture, row) {
295
+ return queryByTestId(fixture, `row-${row}`);
296
+ }
297
+ export function getCellDebugElement(fixture, row, col) {
298
+ const de = fixture.debugElement.query(By.css(`[data-testid="cell-${row}-${col}"]`));
299
+ if (de === null) {
300
+ throw new Error(`Missing cell ${row}:${col}`);
301
+ }
302
+ return de;
303
+ }
304
+ function queryByTestId(fixture, testId) {
305
+ const hostElement = fixture.nativeElement;
306
+ const element = hostElement.querySelector(`[data-testid="${testId}"]`);
307
+ if (element === null) {
308
+ throw new Error(`Missing element with data-testid="${testId}"`);
309
+ }
310
+ return element;
311
+ }
312
+ //# sourceMappingURL=tng-grid.test-harness.js.map