@zengrid/angular 1.2.3 → 1.3.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.
@@ -0,0 +1,10 @@
1
+ import { Directive, TemplateRef, inject } from '@angular/core';
2
+ import type { ZenCellTemplateContext } from '../types';
3
+
4
+ @Directive({
5
+ selector: '[zenCellTemplate]',
6
+ standalone: true,
7
+ })
8
+ export class ZenCellTemplateDirective {
9
+ readonly templateRef = inject(TemplateRef<ZenCellTemplateContext>);
10
+ }
@@ -0,0 +1,10 @@
1
+ import { Directive, TemplateRef, inject } from '@angular/core';
2
+ import type { ZenEditorTemplateContext } from '../types';
3
+
4
+ @Directive({
5
+ selector: '[zenEditorTemplate]',
6
+ standalone: true,
7
+ })
8
+ export class ZenEditorTemplateDirective {
9
+ readonly templateRef = inject(TemplateRef<ZenEditorTemplateContext>);
10
+ }
@@ -0,0 +1,41 @@
1
+ import { Directive, forwardRef, inject } from '@angular/core';
2
+ import { NG_VALUE_ACCESSOR, type ControlValueAccessor } from '@angular/forms';
3
+ import { ZenGridComponent } from '../components/zen-grid.component';
4
+
5
+ @Directive({
6
+ selector: 'zen-grid[formControl],zen-grid[formControlName],zen-grid[ngModel]',
7
+ standalone: true,
8
+ providers: [
9
+ {
10
+ provide: NG_VALUE_ACCESSOR,
11
+ useExisting: forwardRef(() => ZenGridValueAccessorDirective),
12
+ multi: true,
13
+ },
14
+ ],
15
+ })
16
+ export class ZenGridValueAccessorDirective implements ControlValueAccessor {
17
+ private readonly grid = inject(ZenGridComponent);
18
+ private onChange: (value: any[][]) => void = () => {};
19
+ private onTouched: () => void = () => {};
20
+
21
+ constructor() {
22
+ this.grid.dataChange.subscribe(() => {
23
+ this.onChange(this.grid.data());
24
+ this.onTouched();
25
+ });
26
+ }
27
+
28
+ writeValue(value: any[][]): void {
29
+ if (value) {
30
+ this.grid.setData(value);
31
+ }
32
+ }
33
+
34
+ registerOnChange(fn: (value: any[][]) => void): void {
35
+ this.onChange = fn;
36
+ }
37
+
38
+ registerOnTouched(fn: () => void): void {
39
+ this.onTouched = fn;
40
+ }
41
+ }
@@ -0,0 +1,10 @@
1
+ import { Directive, TemplateRef, inject } from '@angular/core';
2
+ import type { ZenHeaderTemplateContext } from '../types';
3
+
4
+ @Directive({
5
+ selector: '[zenHeaderTemplate]',
6
+ standalone: true,
7
+ })
8
+ export class ZenHeaderTemplateDirective {
9
+ readonly templateRef = inject(TemplateRef<ZenHeaderTemplateContext>);
10
+ }
@@ -0,0 +1,40 @@
1
+ import { Injector } from '@angular/core';
2
+ import type {
3
+ GridPlugin,
4
+ GridStore,
5
+ GridApiInterface,
6
+ GridPluginSetupContext,
7
+ PluginDisposable,
8
+ } from '@zengrid/core';
9
+
10
+ export class AngularPluginWrapper implements GridPlugin {
11
+ readonly name: string;
12
+ readonly phase: number;
13
+ readonly dependencies?: string[];
14
+
15
+ constructor(
16
+ private readonly injector: Injector,
17
+ private readonly pluginFactory: (injector: Injector) => GridPlugin,
18
+ ) {
19
+ const plugin = this.pluginFactory(this.injector);
20
+ this.name = plugin.name;
21
+ this.phase = plugin.phase;
22
+ this.dependencies = plugin.dependencies;
23
+ }
24
+
25
+ setup(store: GridStore, api: GridApiInterface, context: GridPluginSetupContext): PluginDisposable | void {
26
+ const plugin = this.pluginFactory(this.injector);
27
+ return plugin.setup(store, api, context);
28
+ }
29
+
30
+ dispose(): void {
31
+ // Plugin cleanup handled by PluginHost
32
+ }
33
+ }
34
+
35
+ export function createAngularPlugin(
36
+ injector: Injector,
37
+ factory: (injector: Injector) => GridPlugin,
38
+ ): GridPlugin {
39
+ return new AngularPluginWrapper(injector, factory);
40
+ }
@@ -0,0 +1,341 @@
1
+ import {
2
+ Injectable,
3
+ TemplateRef,
4
+ ApplicationRef,
5
+ EmbeddedViewRef,
6
+ inject,
7
+ Injector,
8
+ createComponent,
9
+ ComponentRef,
10
+ Type,
11
+ EnvironmentInjector,
12
+ } from '@angular/core';
13
+ import type {
14
+ CellRenderer,
15
+ RenderParams,
16
+ CellEditor,
17
+ EditorParams,
18
+ } from '@zengrid/core';
19
+ import type { ZenCellTemplateContext, ZenEditorTemplateContext } from '../types';
20
+
21
+ interface PooledView<C> {
22
+ viewRef: EmbeddedViewRef<C>;
23
+ inUse: boolean;
24
+ }
25
+
26
+ interface PooledComponent {
27
+ componentRef: ComponentRef<any>;
28
+ inUse: boolean;
29
+ }
30
+
31
+ const MAX_POOL_SIZE = 100;
32
+
33
+ @Injectable({ providedIn: 'root' })
34
+ export class TemplateBridgeService {
35
+ private readonly appRef = inject(ApplicationRef);
36
+ private readonly injector = inject(Injector);
37
+ private readonly envInjector = inject(EnvironmentInjector);
38
+
39
+ private templatePools = new Map<TemplateRef<any>, PooledView<any>[]>();
40
+ private componentPools = new Map<Type<any>, PooledComponent[]>();
41
+
42
+ createTemplateRenderer(templateRef: TemplateRef<ZenCellTemplateContext>): CellRenderer {
43
+ return new TemplateRendererBridge(this, templateRef);
44
+ }
45
+
46
+ createComponentRenderer(componentType: Type<any>): CellRenderer {
47
+ return new ComponentRendererBridge(this, componentType);
48
+ }
49
+
50
+ createTemplateEditor(templateRef: TemplateRef<ZenEditorTemplateContext>): CellEditor {
51
+ return new TemplateEditorBridge(this, templateRef);
52
+ }
53
+
54
+ createComponentEditor(componentType: Type<any>): CellEditor {
55
+ return new ComponentEditorBridge(this, componentType);
56
+ }
57
+
58
+ acquireView<C>(templateRef: TemplateRef<C>, context: C): EmbeddedViewRef<C> {
59
+ let pool = this.templatePools.get(templateRef);
60
+ if (!pool) {
61
+ pool = [];
62
+ this.templatePools.set(templateRef, pool);
63
+ }
64
+
65
+ // Find a pooled view not in use
66
+ const pooled = pool.find(p => !p.inUse);
67
+ if (pooled) {
68
+ pooled.inUse = true;
69
+ // Update context
70
+ Object.assign(pooled.viewRef.context as any, context);
71
+ pooled.viewRef.detectChanges();
72
+ return pooled.viewRef;
73
+ }
74
+
75
+ // Create new
76
+ const viewRef = templateRef.createEmbeddedView(context);
77
+ this.appRef.attachView(viewRef);
78
+
79
+ if (pool.length < MAX_POOL_SIZE) {
80
+ pool.push({ viewRef, inUse: true });
81
+ }
82
+
83
+ return viewRef;
84
+ }
85
+
86
+ releaseView<C>(templateRef: TemplateRef<C>, viewRef: EmbeddedViewRef<C>): void {
87
+ const pool = this.templatePools.get(templateRef);
88
+ if (!pool) return;
89
+
90
+ const pooled = pool.find(p => p.viewRef === viewRef);
91
+ if (pooled) {
92
+ pooled.inUse = false;
93
+ // Detach root nodes from DOM
94
+ for (const node of viewRef.rootNodes) {
95
+ (node as HTMLElement).remove?.();
96
+ }
97
+ } else {
98
+ this.appRef.detachView(viewRef);
99
+ viewRef.destroy();
100
+ }
101
+ }
102
+
103
+ acquireComponent(componentType: Type<any>, hostElement: HTMLElement): ComponentRef<any> {
104
+ let pool = this.componentPools.get(componentType);
105
+ if (!pool) {
106
+ pool = [];
107
+ this.componentPools.set(componentType, pool);
108
+ }
109
+
110
+ const pooled = pool.find(p => !p.inUse);
111
+ if (pooled) {
112
+ pooled.inUse = true;
113
+ hostElement.appendChild((pooled.componentRef.hostView as EmbeddedViewRef<any>).rootNodes[0]);
114
+ return pooled.componentRef;
115
+ }
116
+
117
+ const componentRef = createComponent(componentType, {
118
+ environmentInjector: this.envInjector,
119
+ elementInjector: this.injector,
120
+ hostElement,
121
+ });
122
+ this.appRef.attachView(componentRef.hostView);
123
+
124
+ if (pool.length < MAX_POOL_SIZE) {
125
+ pool.push({ componentRef, inUse: true });
126
+ }
127
+
128
+ return componentRef;
129
+ }
130
+
131
+ releaseComponent(componentType: Type<any>, componentRef: ComponentRef<any>): void {
132
+ const pool = this.componentPools.get(componentType);
133
+ if (!pool) return;
134
+
135
+ const pooled = pool.find(p => p.componentRef === componentRef);
136
+ if (pooled) {
137
+ pooled.inUse = false;
138
+ const rootNode = (componentRef.hostView as EmbeddedViewRef<any>).rootNodes[0] as HTMLElement;
139
+ rootNode?.remove?.();
140
+ } else {
141
+ componentRef.destroy();
142
+ }
143
+ }
144
+
145
+ destroyAll(): void {
146
+ for (const pool of this.templatePools.values()) {
147
+ for (const { viewRef } of pool) {
148
+ this.appRef.detachView(viewRef);
149
+ viewRef.destroy();
150
+ }
151
+ }
152
+ this.templatePools.clear();
153
+
154
+ for (const pool of this.componentPools.values()) {
155
+ for (const { componentRef } of pool) {
156
+ componentRef.destroy();
157
+ }
158
+ }
159
+ this.componentPools.clear();
160
+ }
161
+ }
162
+
163
+ class TemplateRendererBridge implements CellRenderer {
164
+ private activeViews = new Map<HTMLElement, EmbeddedViewRef<ZenCellTemplateContext>>();
165
+
166
+ constructor(
167
+ private bridge: TemplateBridgeService,
168
+ private templateRef: TemplateRef<ZenCellTemplateContext>,
169
+ ) {}
170
+
171
+ render(element: HTMLElement, params: RenderParams): void {
172
+ const context: ZenCellTemplateContext = {
173
+ $implicit: params.value,
174
+ value: params.value,
175
+ cell: params.cell,
176
+ row: params.rowData,
177
+ column: params.column!,
178
+ isSelected: params.isSelected,
179
+ };
180
+
181
+ const viewRef = this.bridge.acquireView(this.templateRef, context);
182
+ for (const node of viewRef.rootNodes) {
183
+ element.appendChild(node);
184
+ }
185
+ this.activeViews.set(element, viewRef);
186
+ }
187
+
188
+ update(element: HTMLElement, params: RenderParams): void {
189
+ const viewRef = this.activeViews.get(element);
190
+ if (!viewRef) {
191
+ this.render(element, params);
192
+ return;
193
+ }
194
+
195
+ const ctx = viewRef.context;
196
+ ctx.$implicit = params.value;
197
+ ctx.value = params.value;
198
+ ctx.cell = params.cell;
199
+ ctx.row = params.rowData;
200
+ ctx.column = params.column!;
201
+ ctx.isSelected = params.isSelected;
202
+ viewRef.detectChanges();
203
+ }
204
+
205
+ destroy(element: HTMLElement): void {
206
+ const viewRef = this.activeViews.get(element);
207
+ if (viewRef) {
208
+ this.bridge.releaseView(this.templateRef, viewRef);
209
+ this.activeViews.delete(element);
210
+ }
211
+ }
212
+ }
213
+
214
+ class ComponentRendererBridge implements CellRenderer {
215
+ private activeComponents = new Map<HTMLElement, ComponentRef<any>>();
216
+
217
+ constructor(
218
+ private bridge: TemplateBridgeService,
219
+ private componentType: Type<any>,
220
+ ) {}
221
+
222
+ render(element: HTMLElement, params: RenderParams): void {
223
+ const componentRef = this.bridge.acquireComponent(this.componentType, element);
224
+ componentRef.setInput('params', params);
225
+ componentRef.changeDetectorRef.detectChanges();
226
+ this.activeComponents.set(element, componentRef);
227
+ }
228
+
229
+ update(element: HTMLElement, params: RenderParams): void {
230
+ const componentRef = this.activeComponents.get(element);
231
+ if (!componentRef) {
232
+ this.render(element, params);
233
+ return;
234
+ }
235
+ componentRef.setInput('params', params);
236
+ componentRef.changeDetectorRef.detectChanges();
237
+ }
238
+
239
+ destroy(element: HTMLElement): void {
240
+ const componentRef = this.activeComponents.get(element);
241
+ if (componentRef) {
242
+ this.bridge.releaseComponent(this.componentType, componentRef);
243
+ this.activeComponents.delete(element);
244
+ }
245
+ }
246
+ }
247
+
248
+ class TemplateEditorBridge implements CellEditor {
249
+ private viewRef: EmbeddedViewRef<ZenEditorTemplateContext> | null = null;
250
+ private currentValue: any = null;
251
+ private onCompleteFn: ((value: any, cancelled: boolean) => void) | null = null;
252
+
253
+ constructor(
254
+ private bridge: TemplateBridgeService,
255
+ private templateRef: TemplateRef<ZenEditorTemplateContext>,
256
+ ) {}
257
+
258
+ init(container: HTMLElement, value: any, params: EditorParams): void {
259
+ this.currentValue = value;
260
+ this.onCompleteFn = params.onComplete ?? null;
261
+
262
+ const context: ZenEditorTemplateContext = {
263
+ $implicit: value,
264
+ value,
265
+ cell: params.cell,
266
+ onComplete: (v: any) => {
267
+ this.currentValue = v;
268
+ this.onCompleteFn?.(v, false);
269
+ },
270
+ onChange: (v: any) => {
271
+ this.currentValue = v;
272
+ params.onChange?.(v);
273
+ },
274
+ };
275
+
276
+ this.viewRef = this.bridge.acquireView(this.templateRef, context);
277
+ for (const node of this.viewRef.rootNodes) {
278
+ container.appendChild(node);
279
+ }
280
+ }
281
+
282
+ getValue(): any {
283
+ return this.currentValue;
284
+ }
285
+
286
+ focus(): void {
287
+ if (!this.viewRef) return;
288
+ const firstInput = this.viewRef.rootNodes
289
+ .find((n: any) => n.querySelector?.('input,select,textarea'));
290
+ firstInput?.querySelector?.('input,select,textarea')?.focus();
291
+ }
292
+
293
+ destroy(): void {
294
+ if (this.viewRef) {
295
+ this.bridge.releaseView(this.templateRef, this.viewRef);
296
+ this.viewRef = null;
297
+ }
298
+ }
299
+ }
300
+
301
+ class ComponentEditorBridge implements CellEditor {
302
+ private componentRef: ComponentRef<any> | null = null;
303
+ private currentValue: any = null;
304
+
305
+ constructor(
306
+ private bridge: TemplateBridgeService,
307
+ private componentType: Type<any>,
308
+ ) {}
309
+
310
+ init(container: HTMLElement, value: any, params: EditorParams): void {
311
+ this.currentValue = value;
312
+ this.componentRef = this.bridge.acquireComponent(this.componentType, container);
313
+ this.componentRef.setInput('value', value);
314
+ this.componentRef.setInput('params', params);
315
+ this.componentRef.setInput('onComplete', (v: any) => {
316
+ this.currentValue = v;
317
+ params.onComplete?.(v, false);
318
+ });
319
+ this.componentRef.setInput('onChange', (v: any) => {
320
+ this.currentValue = v;
321
+ params.onChange?.(v);
322
+ });
323
+ this.componentRef.changeDetectorRef.detectChanges();
324
+ }
325
+
326
+ getValue(): any {
327
+ return this.currentValue;
328
+ }
329
+
330
+ focus(): void {
331
+ const hostEl = this.componentRef?.location?.nativeElement as HTMLElement;
332
+ (hostEl?.querySelector?.('input,select,textarea') as HTMLElement)?.focus();
333
+ }
334
+
335
+ destroy(): void {
336
+ if (this.componentRef) {
337
+ this.bridge.releaseComponent(this.componentType, this.componentRef);
338
+ this.componentRef = null;
339
+ }
340
+ }
341
+ }
@@ -0,0 +1,11 @@
1
+ import { InjectionToken, type Provider } from '@angular/core';
2
+ import type { ZenGridConfig } from '../types';
3
+
4
+ export const ZEN_GRID_CONFIG = new InjectionToken<ZenGridConfig>('ZenGridConfig');
5
+
6
+ export function provideZenGrid(config: ZenGridConfig): Provider {
7
+ return {
8
+ provide: ZEN_GRID_CONFIG,
9
+ useValue: config,
10
+ };
11
+ }
@@ -0,0 +1,107 @@
1
+ import type { TemplateRef, Type } from '@angular/core';
2
+ import type {
3
+ CellRef,
4
+ CellRange,
5
+ CellPosition,
6
+ ColumnDef,
7
+ GridOptions,
8
+ GridEvents,
9
+ GridState,
10
+ GridStateSnapshot,
11
+ ColumnStateSnapshot,
12
+ GridExportOptions,
13
+ SortState,
14
+ SortDescriptor,
15
+ FilterModel,
16
+ FilterAst,
17
+ FilterAstNode,
18
+ FilterAstTarget,
19
+ FilterValueType,
20
+ QueryState,
21
+ QueryPaginationState,
22
+ DataLoadRequest,
23
+ DataLoadResponse,
24
+ RenderParams,
25
+ EditorParams,
26
+ HeaderRenderParams,
27
+ GridPlugin,
28
+ GridStore,
29
+ GridApiInterface,
30
+ GridPluginSetupContext,
31
+ GridOptionsChange,
32
+ GridOptionsListener,
33
+ CellRenderer,
34
+ CellEditor,
35
+ HeaderRenderer,
36
+ } from '@zengrid/core';
37
+
38
+ export type {
39
+ CellRef,
40
+ CellRange,
41
+ CellPosition,
42
+ ColumnDef,
43
+ GridOptions,
44
+ GridEvents,
45
+ GridState,
46
+ GridStateSnapshot,
47
+ ColumnStateSnapshot,
48
+ GridExportOptions,
49
+ SortState,
50
+ SortDescriptor,
51
+ FilterModel,
52
+ FilterAst,
53
+ FilterAstNode,
54
+ FilterAstTarget,
55
+ FilterValueType,
56
+ QueryState,
57
+ QueryPaginationState,
58
+ DataLoadRequest,
59
+ DataLoadResponse,
60
+ RenderParams,
61
+ EditorParams,
62
+ HeaderRenderParams,
63
+ GridPlugin,
64
+ GridStore,
65
+ GridApiInterface,
66
+ GridPluginSetupContext,
67
+ GridOptionsChange,
68
+ GridOptionsListener,
69
+ CellRenderer,
70
+ CellEditor,
71
+ HeaderRenderer,
72
+ };
73
+
74
+ export interface ZenCellTemplateContext {
75
+ $implicit: any;
76
+ value: any;
77
+ cell: CellRef;
78
+ row: any;
79
+ column: ColumnDef;
80
+ isSelected: boolean;
81
+ }
82
+
83
+ export interface ZenEditorTemplateContext {
84
+ $implicit: any;
85
+ value: any;
86
+ cell: CellRef;
87
+ onComplete: (value: any) => void;
88
+ onChange: (value: any) => void;
89
+ }
90
+
91
+ export interface ZenHeaderTemplateContext {
92
+ $implicit: ColumnDef;
93
+ column: ColumnDef;
94
+ sortState: 'asc' | 'desc' | null;
95
+ }
96
+
97
+ export interface ZenGridConfig {
98
+ [key: string]: any;
99
+ }
100
+
101
+ export type RendererInput = string | CellRenderer | TemplateRef<ZenCellTemplateContext> | Type<any>;
102
+ export type EditorInput = string | CellEditor | TemplateRef<ZenEditorTemplateContext> | Type<any>;
103
+ export type HeaderRendererInput =
104
+ | string
105
+ | HeaderRenderer
106
+ | TemplateRef<ZenHeaderTemplateContext>
107
+ | Type<any>;
@@ -0,0 +1,98 @@
1
+ export const EVENT_MAP: Record<string, string> = {
2
+ // Cell events
3
+ 'cell:click': 'cellClick',
4
+ 'cell:doubleClick': 'cellDoubleClick',
5
+ 'cell:contextMenu': 'cellContextMenu',
6
+ 'cell:change': 'cellChange',
7
+ 'cell:beforeChange': 'cellBeforeChange',
8
+ 'cell:afterChange': 'cellAfterChange',
9
+
10
+ // Selection events
11
+ 'selection:change': 'selectionChange',
12
+ 'selection:start': 'selectionStart',
13
+ 'selection:end': 'selectionEnd',
14
+
15
+ // Editing events
16
+ 'edit:start': 'editStart',
17
+ 'edit:end': 'editEnd',
18
+ 'edit:commit': 'editCommit',
19
+ 'edit:cancel': 'editCancel',
20
+
21
+ // Scroll events
22
+ 'scroll': 'scroll',
23
+ 'scroll:start': 'scrollStart',
24
+ 'scroll:end': 'scrollEnd',
25
+
26
+ // Sort events
27
+ 'sort:change': 'sortChange',
28
+ 'sort:beforeSort': 'sortBeforeSort',
29
+ 'sort:afterSort': 'sortAfterSort',
30
+
31
+ // Filter events
32
+ 'filter:change': 'filterChange',
33
+ 'filter:beforeFilter': 'filterBeforeFilter',
34
+ 'filter:afterFilter': 'filterAfterFilter',
35
+ 'filter:export': 'filterExport',
36
+ 'filter:start': 'filterStart',
37
+ 'filter:end': 'filterEnd',
38
+ 'filter:error': 'filterError',
39
+ 'filter:clear': 'filterClear',
40
+
41
+ // Focus events
42
+ 'focus:change': 'focusChange',
43
+ 'focus:in': 'focusIn',
44
+ 'focus:out': 'focusOut',
45
+
46
+ // Keyboard events
47
+ 'key:down': 'keyDown',
48
+ 'key:up': 'keyUp',
49
+ 'key:press': 'keyPress',
50
+
51
+ // Column events
52
+ 'column:resize': 'columnResize',
53
+ 'column:move': 'columnMove',
54
+ 'column:hide': 'columnHide',
55
+ 'column:show': 'columnShow',
56
+ 'column:dragStart': 'columnDragStart',
57
+ 'column:drag': 'columnDrag',
58
+ 'column:dragEnd': 'columnDragEnd',
59
+ 'column:dragCancel': 'columnDragCancel',
60
+
61
+ // Header events
62
+ 'header:click': 'headerClick',
63
+ 'header:doubleClick': 'headerDoubleClick',
64
+ 'header:contextMenu': 'headerContextMenu',
65
+ 'header:hover': 'headerHover',
66
+ 'header:sort:click': 'headerSortClick',
67
+ 'header:filter:click': 'headerFilterClick',
68
+ 'header:checkbox:change': 'headerCheckboxChange',
69
+
70
+ // Lifecycle events
71
+ 'render:start': 'renderStart',
72
+ 'render:end': 'renderEnd',
73
+ 'data:load': 'dataLoad',
74
+ 'data:change': 'dataChange',
75
+ 'loading:start': 'loadingStart',
76
+ 'loading:end': 'loadingEnd',
77
+ 'loading:progress': 'loadingProgress',
78
+
79
+ // Undo/Redo events
80
+ 'undo-redo:change': 'undoRedoChange',
81
+
82
+ // Destroy
83
+ 'destroy': 'gridDestroy',
84
+
85
+ // Error events
86
+ 'error': 'gridError',
87
+ 'warning': 'gridWarning',
88
+
89
+ // Row events
90
+ 'row:insert': 'rowInsert',
91
+ 'row:delete': 'rowDelete',
92
+ 'row:move': 'rowMove',
93
+
94
+ // Clipboard events
95
+ 'copy': 'copy',
96
+ 'cut': 'cut',
97
+ 'paste': 'paste',
98
+ };
@@ -0,0 +1,17 @@
1
+ import { signal, type Signal, type WritableSignal } from '@angular/core';
2
+ import type { GridStore } from '@zengrid/core';
3
+
4
+ export function bridgeStoreSignal<T>(store: GridStore, key: string): Signal<T> {
5
+ const angularSignal: WritableSignal<T> = signal(store.get(key) as T);
6
+
7
+ store.effect(
8
+ `angular-bridge:${key}`,
9
+ () => {
10
+ const value = store.get(key) as T;
11
+ angularSignal.set(value);
12
+ },
13
+ 'angular-bridge'
14
+ );
15
+
16
+ return angularSignal.asReadonly();
17
+ }
@@ -0,0 +1,7 @@
1
+ import { PLATFORM_ID, inject } from '@angular/core';
2
+ import { isPlatformBrowser } from '@angular/common';
3
+
4
+ export function isBrowser(): boolean {
5
+ const platformId = inject(PLATFORM_ID);
6
+ return isPlatformBrowser(platformId);
7
+ }