@ssv/ngx.ux 3.0.0-dev.36 → 3.0.0-dev.38

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 (71) hide show
  1. package/esm2022/index.mjs +4 -0
  2. package/esm2022/internal/internal.model.mjs +2 -0
  3. package/esm2022/platform/window.mjs +31 -0
  4. package/esm2022/ssv-ngx.ux.mjs +5 -0
  5. package/esm2022/ux.module.mjs +19 -0
  6. package/esm2022/version.mjs +2 -0
  7. package/esm2022/viewport/index.mjs +10 -0
  8. package/esm2022/viewport/viewport-data/index.mjs +4 -0
  9. package/esm2022/viewport/viewport-data/viewport-data-matcher.mjs +107 -0
  10. package/esm2022/viewport/viewport-data/viewport-data.pipe.mjs +48 -0
  11. package/esm2022/viewport/viewport-data/viewport-data.service.mjs +32 -0
  12. package/esm2022/viewport/viewport-data/viewport-data.utils.mjs +100 -0
  13. package/esm2022/viewport/viewport-matcher-var.directive.mjs +69 -0
  14. package/esm2022/viewport/viewport-matcher.directive.mjs +136 -0
  15. package/esm2022/viewport/viewport-server-size.service.mjs +37 -0
  16. package/esm2022/viewport/viewport.model.mjs +30 -0
  17. package/esm2022/viewport/viewport.module.mjs +27 -0
  18. package/esm2022/viewport/viewport.options.mjs +51 -0
  19. package/esm2022/viewport/viewport.service.mjs +82 -0
  20. package/esm2022/viewport/viewport.util.mjs +117 -0
  21. package/fesm2022/ssv-ngx.ux.mjs +846 -0
  22. package/fesm2022/ssv-ngx.ux.mjs.map +1 -0
  23. package/{src/index.ts → index.d.ts} +0 -1
  24. package/{src/internal/internal.model.ts → internal/internal.model.d.ts} +1 -1
  25. package/package.json +18 -3
  26. package/platform/window.d.ts +13 -0
  27. package/ux.module.d.ts +7 -0
  28. package/version.d.ts +1 -0
  29. package/{src/viewport/index.ts → viewport/index.d.ts} +1 -12
  30. package/viewport/viewport-data/viewport-data-matcher.d.ts +32 -0
  31. package/viewport/viewport-data/viewport-data.pipe.d.ts +18 -0
  32. package/viewport/viewport-data/viewport-data.service.d.ts +17 -0
  33. package/viewport/viewport-data/viewport-data.utils.d.ts +21 -0
  34. package/viewport/viewport-matcher-var.directive.d.ts +25 -0
  35. package/viewport/viewport-matcher.directive.d.ts +33 -0
  36. package/viewport/viewport-server-size.service.d.ts +10 -0
  37. package/viewport/viewport.model.d.ts +47 -0
  38. package/viewport/viewport.module.d.ts +9 -0
  39. package/viewport/viewport.options.d.ts +19 -0
  40. package/viewport/viewport.service.d.ts +36 -0
  41. package/viewport/viewport.util.d.ts +25 -0
  42. package/eslint.config.js +0 -43
  43. package/index.ts +0 -1
  44. package/jest.config.ts +0 -21
  45. package/ng-package.json +0 -7
  46. package/project.json +0 -36
  47. package/src/platform/window.ts +0 -31
  48. package/src/test-setup.ts +0 -8
  49. package/src/ux.module.ts +0 -15
  50. package/src/version.ts +0 -1
  51. package/src/viewport/viewport-data/README.md +0 -47
  52. package/src/viewport/viewport-data/viewport-data-matcher.spec.ts +0 -227
  53. package/src/viewport/viewport-data/viewport-data-matcher.ts +0 -175
  54. package/src/viewport/viewport-data/viewport-data.pipe.ts +0 -51
  55. package/src/viewport/viewport-data/viewport-data.service.ts +0 -48
  56. package/src/viewport/viewport-data/viewport-data.utils.spec.ts +0 -228
  57. package/src/viewport/viewport-data/viewport-data.utils.ts +0 -137
  58. package/src/viewport/viewport-matcher-var.directive.ts +0 -85
  59. package/src/viewport/viewport-matcher.directive.ts +0 -170
  60. package/src/viewport/viewport-server-size.service.ts +0 -37
  61. package/src/viewport/viewport.model.ts +0 -54
  62. package/src/viewport/viewport.module.ts +0 -19
  63. package/src/viewport/viewport.options.ts +0 -74
  64. package/src/viewport/viewport.service.ts +0 -123
  65. package/src/viewport/viewport.util.spec.ts +0 -254
  66. package/src/viewport/viewport.util.ts +0 -152
  67. package/tsconfig.json +0 -28
  68. package/tsconfig.lib.json +0 -12
  69. package/tsconfig.lib.prod.json +0 -9
  70. package/tsconfig.spec.json +0 -11
  71. /package/{src/viewport/viewport-data/index.ts → viewport/viewport-data/index.d.ts} +0 -0
@@ -0,0 +1,846 @@
1
+ import * as i0 from '@angular/core';
2
+ import { InjectionToken, inject, Injectable, makeEnvironmentProviders, Pipe, Directive, Input, NgModule } from '@angular/core';
3
+ import { fromEvent, map, share, auditTime, of, startWith, distinctUntilChanged, shareReplay, tap, Subscription, Subject, ReplaySubject, combineLatest, takeUntil, filter, pairwise } from 'rxjs';
4
+
5
+ var ViewportDataMatchStrategy;
6
+ (function (ViewportDataMatchStrategy) {
7
+ /** Indicates that size should match exact or default. */
8
+ ViewportDataMatchStrategy[ViewportDataMatchStrategy["exact"] = 0] = "exact";
9
+ /** Indicates that size matches when exact match, first match smaller (down) or default. */
10
+ ViewportDataMatchStrategy[ViewportDataMatchStrategy["smaller"] = 1] = "smaller";
11
+ /** Indicates that size matches when exact match, first match larger (up) or default. */
12
+ ViewportDataMatchStrategy[ViewportDataMatchStrategy["larger"] = 2] = "larger";
13
+ /** Indicates that size matches when exact match, or it tries both smaller/larger (smaller is preferred) until match or default. */
14
+ ViewportDataMatchStrategy[ViewportDataMatchStrategy["closestSmallerFirst"] = 3] = "closestSmallerFirst";
15
+ /** Indicates that size matches when exact match, or it tries both larger/smaller (larger is preferred) until match or default. */
16
+ ViewportDataMatchStrategy[ViewportDataMatchStrategy["closestLargerFirst"] = 4] = "closestLargerFirst";
17
+ })(ViewportDataMatchStrategy || (ViewportDataMatchStrategy = {}));
18
+ /**
19
+ * Utility function to match data based on strategy and size.
20
+ *
21
+ * @param dataConfig Data config to generate rules based on.
22
+ * @param sizeType Size type to get data for.
23
+ * @param strategy Strategy to use when building rules.
24
+ * @param sizeTypes Available size types ordered by index type. (Can be obtained from `ViewportService`)
25
+ * @param sizeTypeMap Available size type map. (Can be obtained from `ViewportService`)
26
+ * @returns Returns the matched data value.
27
+ */
28
+ function matchViewportData(dataConfig, sizeType, strategy, sizeTypes, sizeTypeMap) {
29
+ const matchFn = matchStrategyHandlerMap$1[strategy];
30
+ if (!matchFn) {
31
+ throw Error(`matchViewportData: Viewport Data strategy not implemented. Strategy: '${strategy}'`);
32
+ }
33
+ const data = matchFn(dataConfig, sizeType, sizeTypes, sizeTypeMap);
34
+ if (data !== undefined) {
35
+ return data;
36
+ }
37
+ return dataConfig.default;
38
+ }
39
+ const matchStrategyHandlerMap$1 = {
40
+ [ViewportDataMatchStrategy.exact]: matchWithExact,
41
+ [ViewportDataMatchStrategy.larger]: matchWithLargerMatch,
42
+ [ViewportDataMatchStrategy.smaller]: matchWithSmallerMatch,
43
+ [ViewportDataMatchStrategy.closestSmallerFirst]: matchWithClosestSmallerFirstMatch,
44
+ [ViewportDataMatchStrategy.closestLargerFirst]: matchWithClosestLargerFirstMatch,
45
+ };
46
+ function matchWithExact(dataConfig, currentSizeType) {
47
+ return dataConfig[currentSizeType.name];
48
+ }
49
+ function matchWithLargerMatch(dataConfig, currentSizeType, sizeTypes) {
50
+ let data = dataConfig[currentSizeType.name];
51
+ if (data !== undefined) {
52
+ return data;
53
+ }
54
+ const largestTypeIdx = sizeTypes[sizeTypes.length - 1].type;
55
+ if (currentSizeType.type >= largestTypeIdx) {
56
+ return undefined;
57
+ }
58
+ for (let index = currentSizeType.type; index < sizeTypes.length; index++) {
59
+ const sizeType = sizeTypes[index];
60
+ data = dataConfig[sizeType.name];
61
+ if (data !== undefined) {
62
+ return data;
63
+ }
64
+ }
65
+ return undefined;
66
+ }
67
+ function matchWithSmallerMatch(dataConfig, currentSizeType, sizeTypes) {
68
+ let data = dataConfig[currentSizeType.name];
69
+ if (data !== undefined) {
70
+ return data;
71
+ }
72
+ if (currentSizeType.type <= 0) {
73
+ return undefined;
74
+ }
75
+ // eslint-disable-next-line for-direction
76
+ for (let index = currentSizeType.type; index < sizeTypes.length; index--) {
77
+ const sizeType = sizeTypes[index];
78
+ data = dataConfig[sizeType.name];
79
+ if (data !== undefined) {
80
+ return data;
81
+ }
82
+ }
83
+ return undefined;
84
+ }
85
+ function matchWithClosestSmallerFirstMatch(dataConfig, currentSizeType, sizeTypes) {
86
+ return closestMatch(dataConfig, currentSizeType, sizeTypes, true);
87
+ }
88
+ function matchWithClosestLargerFirstMatch(dataConfig, currentSizeType, sizeTypes) {
89
+ return closestMatch(dataConfig, currentSizeType, sizeTypes, false);
90
+ }
91
+ function closestMatch(dataConfig, currentSizeType, sizeTypes, isSmallerFirst) {
92
+ let data = dataConfig[currentSizeType.name];
93
+ if (data !== undefined) {
94
+ return data;
95
+ }
96
+ let downIndex = currentSizeType.type;
97
+ let upIndex = currentSizeType.type;
98
+ for (let index = 0; index < sizeTypes.length; index++) {
99
+ for (const idx of isSmallerFirst ? [--downIndex, ++upIndex] : [++upIndex, --downIndex]) {
100
+ const sizeType = sizeTypes[idx];
101
+ if (sizeType) {
102
+ data = dataConfig[sizeType.name];
103
+ if (data !== undefined) {
104
+ return data;
105
+ }
106
+ }
107
+ }
108
+ }
109
+ return undefined;
110
+ }
111
+
112
+ const WINDOW = new InjectionToken("Window", {
113
+ factory: () => _window(),
114
+ });
115
+ function _window() {
116
+ if (typeof window !== "undefined") {
117
+ return window;
118
+ }
119
+ return {};
120
+ }
121
+ class WindowRef {
122
+ window = inject(WINDOW);
123
+ /** Window underlying native object. */
124
+ get native() {
125
+ return this.window;
126
+ }
127
+ /** Determines whether native element is supported or not. Generally `false` when executing in SSR. */
128
+ get hasNative() {
129
+ return !!this.native.window;
130
+ }
131
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.9", ngImport: i0, type: WindowRef, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
132
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.2.9", ngImport: i0, type: WindowRef, providedIn: "root" });
133
+ }
134
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.9", ngImport: i0, type: WindowRef, decorators: [{
135
+ type: Injectable,
136
+ args: [{
137
+ providedIn: "root",
138
+ }]
139
+ }] });
140
+
141
+ /**
142
+ * The indices of each breakpoint provided based on the `UX_VIEWPORT_DEFAULT_BREAKPOINTS`.
143
+ * @see UX_VIEWPORT_DEFAULT_BREAKPOINTS
144
+ */
145
+ var ViewportSizeType;
146
+ (function (ViewportSizeType) {
147
+ ViewportSizeType[ViewportSizeType["xsmall"] = 0] = "xsmall";
148
+ ViewportSizeType[ViewportSizeType["small"] = 1] = "small";
149
+ ViewportSizeType[ViewportSizeType["medium"] = 2] = "medium";
150
+ ViewportSizeType[ViewportSizeType["large"] = 3] = "large";
151
+ ViewportSizeType[ViewportSizeType["xlarge"] = 4] = "xlarge";
152
+ ViewportSizeType[ViewportSizeType["xxlarge"] = 5] = "xxlarge";
153
+ ViewportSizeType[ViewportSizeType["xxlarge1"] = 6] = "xxlarge1";
154
+ })(ViewportSizeType || (ViewportSizeType = {}));
155
+ var ComparisonOperation;
156
+ (function (ComparisonOperation) {
157
+ ComparisonOperation["equals"] = "=";
158
+ ComparisonOperation["notEquals"] = "<>";
159
+ ComparisonOperation["lessThan"] = "<";
160
+ ComparisonOperation["lessOrEqualThan"] = "<=";
161
+ ComparisonOperation["greaterThan"] = ">";
162
+ ComparisonOperation["greaterOrEqualThan"] = ">=";
163
+ })(ComparisonOperation || (ComparisonOperation = {}));
164
+ var DeviceType;
165
+ (function (DeviceType) {
166
+ DeviceType["desktop"] = "desktop";
167
+ DeviceType["mobile"] = "mobile";
168
+ DeviceType["tablet"] = "tablet";
169
+ })(DeviceType || (DeviceType = {}));
170
+
171
+ // todo: make this configurable
172
+ /** Viewport size for SSR. */
173
+ const viewportSizeSSR = {
174
+ [DeviceType.desktop]: {
175
+ width: 1366,
176
+ height: 768,
177
+ },
178
+ [DeviceType.tablet]: {
179
+ width: 768,
180
+ height: 1024,
181
+ },
182
+ [DeviceType.mobile]: {
183
+ width: 414,
184
+ height: 736,
185
+ },
186
+ };
187
+ const VIEWPORT_SSR_DEVICE = new InjectionToken("UX_VIEWPORT_SSR_DEVICE", {
188
+ factory: () => DeviceType.desktop,
189
+ });
190
+ class ViewportServerSizeService {
191
+ deviceType = inject(VIEWPORT_SSR_DEVICE);
192
+ get() {
193
+ return viewportSizeSSR[this.deviceType] || viewportSizeSSR[DeviceType.desktop];
194
+ }
195
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.9", ngImport: i0, type: ViewportServerSizeService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
196
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.2.9", ngImport: i0, type: ViewportServerSizeService, providedIn: "root" });
197
+ }
198
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.9", ngImport: i0, type: ViewportServerSizeService, decorators: [{
199
+ type: Injectable,
200
+ args: [{
201
+ providedIn: "root",
202
+ }]
203
+ }] });
204
+
205
+ function isViewportSizeMatcherExpression(value) {
206
+ if (typeof value !== "object" || !value) {
207
+ return false;
208
+ }
209
+ const args = value;
210
+ if (args.size && args.operation) {
211
+ return true;
212
+ }
213
+ return false;
214
+ }
215
+ function isViewportSizeMatcherTupleExpression(arg) {
216
+ if (!arg) {
217
+ return false;
218
+ }
219
+ if (Array.isArray(arg)) {
220
+ if (arg.length === 2) {
221
+ const [op] = arg;
222
+ return operations.includes(op);
223
+ }
224
+ }
225
+ return false;
226
+ }
227
+ const operations = Object.values(ComparisonOperation);
228
+ const COMPARISON_OPERATION_FUNC_MAPPING = {
229
+ [ComparisonOperation.equals]: (a, b) => a === b,
230
+ [ComparisonOperation.notEquals]: (a, b) => a !== b,
231
+ [ComparisonOperation.lessThan]: (a, b) => a < b,
232
+ [ComparisonOperation.lessOrEqualThan]: (a, b) => a <= b,
233
+ [ComparisonOperation.greaterThan]: (a, b) => a > b,
234
+ [ComparisonOperation.greaterOrEqualThan]: (a, b) => a >= b,
235
+ };
236
+ function isViewportConditionMatch(evaluateSize, conditions, viewportSizeTypeInfoRefs) {
237
+ const isExcluded = match(conditions.sizeTypeExclude, evaluateSize.name, false);
238
+ let isIncluded;
239
+ let isExpressionTruthy;
240
+ if (!isExcluded && conditions.expression) {
241
+ const ref = viewportSizeTypeInfoRefs[conditions.expression.size];
242
+ if (!ref) {
243
+ throw new Error(`Viewport size type is invalid. Size type: '${conditions.expression.size}'`);
244
+ }
245
+ const expMatcher = COMPARISON_OPERATION_FUNC_MAPPING[conditions.expression.operation];
246
+ isExpressionTruthy = expMatcher(evaluateSize.type, ref.type);
247
+ }
248
+ else {
249
+ isIncluded = match(conditions.sizeType, evaluateSize.name, true);
250
+ }
251
+ const shouldRender = (isExpressionTruthy || isIncluded) && !isExcluded;
252
+ // console.warn(">>> shouldRender", { evaluateSize, conditions, shouldRender });
253
+ return !!shouldRender;
254
+ }
255
+ function match(value, targetValue, defaultValue) {
256
+ if (!value) {
257
+ return defaultValue;
258
+ }
259
+ return Array.isArray(value)
260
+ ? value.includes(targetValue)
261
+ : value === targetValue;
262
+ }
263
+ function getSizeTypeInfo(width, sizeTypes) {
264
+ const lastEntryIndex = sizeTypes.length - 1;
265
+ for (let idx = 0; idx < lastEntryIndex; idx++) {
266
+ const viewportSizeTypeInfo = sizeTypes[idx];
267
+ if (width <= viewportSizeTypeInfo.widthThreshold) {
268
+ return viewportSizeTypeInfo;
269
+ }
270
+ }
271
+ return sizeTypes[lastEntryIndex];
272
+ }
273
+ /**
274
+ * Converts the breakpoints into a 2 dimensional array containing the name and width, and sorted from
275
+ * smallest to largest.
276
+ * @param breakpoints the breakpoints obtained from the config
277
+ * @internal
278
+ */
279
+ function getSortedBreakpoints(breakpoints) {
280
+ return Object.entries(breakpoints)
281
+ .sort(([, widthA], [, widthB]) => widthA - widthB);
282
+ }
283
+ /**
284
+ * A util function which generates the ViewportSizeTypeInfo.type for each breakpoint.
285
+ * @param breakpoints the custom breakpoints
286
+ */
287
+ function generateViewportSizeType(breakpoints) {
288
+ return Object.freeze(getSortedBreakpoints(breakpoints).reduce((dictionary, [name], index) => {
289
+ dictionary[name] = index;
290
+ dictionary[index] = name;
291
+ return dictionary;
292
+ }, {}));
293
+ }
294
+ /**
295
+ * Pre-processes the given breakpoints into an ordered list from smallest to largest while generating
296
+ * all the necessary information on the viewport.
297
+ * @param breakpoints the breakpoints obtained from the config
298
+ * @internal
299
+ */
300
+ function generateViewportSizeTypeInfoList(breakpoints) {
301
+ return getSortedBreakpoints(breakpoints)
302
+ .map(([name, width], index) => (Object.freeze({
303
+ name,
304
+ type: index,
305
+ widthThreshold: width
306
+ })));
307
+ }
308
+ /**
309
+ * Converts the breakpoint list into a dictionary while using the name as key.
310
+ * @param breakpointList the list of breakpoints
311
+ * @internal
312
+ */
313
+ function generateViewportSizeTypeInfoRefs(breakpointList) {
314
+ return Object.freeze(breakpointList.reduce((dictionary, breakpoint) => {
315
+ dictionary[breakpoint.name] = breakpoint;
316
+ dictionary[breakpoint.type] = breakpoint;
317
+ return dictionary;
318
+ }, {}));
319
+ }
320
+
321
+ /** Default viewport breakpoints. */
322
+ const UX_VIEWPORT_DEFAULT_BREAKPOINTS = {
323
+ xsmall: 450,
324
+ small: 767,
325
+ medium: 992,
326
+ large: 1200,
327
+ xlarge: 1500,
328
+ xxlarge: 1920,
329
+ xxlarge1: 2100,
330
+ };
331
+ const DEFAULT_OPTIONS = Object.freeze({
332
+ resizePollingSpeed: 33,
333
+ breakpoints: UX_VIEWPORT_DEFAULT_BREAKPOINTS,
334
+ defaultDataMatchStrategy: ViewportDataMatchStrategy.smaller,
335
+ });
336
+ const VIEWPORT_OPTIONS = new InjectionToken("SSV_UX_VIEWPORT_OPTIONS", {
337
+ factory: () => DEFAULT_OPTIONS,
338
+ });
339
+ function provideSsvUxViewportOptions(options, ...features) {
340
+ return makeEnvironmentProviders([
341
+ {
342
+ provide: VIEWPORT_OPTIONS,
343
+ useFactory: () => {
344
+ let opts = typeof options === "function" ? options(DEFAULT_OPTIONS) : options;
345
+ opts = opts
346
+ ? {
347
+ ...DEFAULT_OPTIONS,
348
+ ...opts, // NOTE: breakpoints shoudn't be merged
349
+ }
350
+ : DEFAULT_OPTIONS;
351
+ return opts;
352
+ },
353
+ },
354
+ ...features,
355
+ ]);
356
+ }
357
+ function withViewportSsrDevice(options) {
358
+ return makeEnvironmentProviders([
359
+ {
360
+ provide: VIEWPORT_SSR_DEVICE,
361
+ useFactory: () => {
362
+ const deviceType = typeof options === "function" ? options() : options;
363
+ return deviceType;
364
+ },
365
+ },
366
+ ]);
367
+ }
368
+
369
+ class ViewportService {
370
+ windowRef;
371
+ viewportServerSize;
372
+ /** Window resize observable. */
373
+ resizeSnap$;
374
+ /** Window resize observable (which is also throttled). */
375
+ resize$;
376
+ /** Viewport size type observable (which is also throttled). */
377
+ sizeType$;
378
+ /** Viewport size type observable. */
379
+ sizeTypeSnap$;
380
+ /** Viewport size type snapshot of the last value. (Prefer use `sizeType$` observable when possible.) */
381
+ get sizeTypeSnapshot() { return this._sizeTypeSnapshot; }
382
+ /** Viewport size observable (which is also throttled). */
383
+ size$;
384
+ /** Viewport size observable. */
385
+ sizeSnap$;
386
+ /** Size types refs of the generated viewport size type info. */
387
+ get sizeTypeMap() { return this._sizeTypeMap; }
388
+ /** Viewport size types list ordered by type, smallest to largest. */
389
+ get sizeTypes() { return this._sizeTypes; }
390
+ _sizeTypeMap;
391
+ _sizeTypes;
392
+ _sizeTypeSnapshot;
393
+ constructor(windowRef, viewportServerSize) {
394
+ this.windowRef = windowRef;
395
+ this.viewportServerSize = viewportServerSize;
396
+ const config = inject(VIEWPORT_OPTIONS);
397
+ this._sizeTypes = generateViewportSizeTypeInfoList(config.breakpoints);
398
+ this._sizeTypeMap = generateViewportSizeTypeInfoRefs(this._sizeTypes);
399
+ if (windowRef.hasNative) {
400
+ this.resizeSnap$ = fromEvent(window, "resize").pipe(map(() => this.getViewportSize()), share());
401
+ this.resize$ = this.resizeSnap$.pipe(auditTime(config.resizePollingSpeed), share());
402
+ }
403
+ else {
404
+ this.resizeSnap$ = this.resize$ = of(viewportServerSize.get());
405
+ }
406
+ const size = this.getViewportSize();
407
+ this._sizeTypeSnapshot = getSizeTypeInfo(size.width, this.sizeTypes);
408
+ const sizeFn = (obs$) => obs$.pipe(startWith(size), distinctUntilChanged((a, b) => a.width === b.width && a.height === b.height), shareReplay(1));
409
+ this.sizeSnap$ = sizeFn(this.resizeSnap$);
410
+ this.size$ = sizeFn(this.resize$);
411
+ const sizeTypeFn = (obs$) => obs$.pipe(distinctUntilChanged((a, b) => a.width === b.width), map(x => getSizeTypeInfo(x.width, this.sizeTypes)), distinctUntilChanged(), tap(x => this._sizeTypeSnapshot = x), shareReplay(1));
412
+ this.sizeType$ = sizeTypeFn(this.size$);
413
+ this.sizeTypeSnap$ = sizeTypeFn(this.sizeSnap$);
414
+ }
415
+ /** Returns the current viewport size */
416
+ getViewportSize() {
417
+ if (!this.windowRef.hasNative) {
418
+ return this.viewportServerSize.get();
419
+ }
420
+ const ua = navigator.userAgent.toLowerCase();
421
+ if (ua.indexOf("safari") !== -1 && ua.indexOf("chrome") === -1) { // safari subtracts the scrollbar width
422
+ return {
423
+ width: this.windowRef.native.document.documentElement.clientWidth,
424
+ height: this.windowRef.native.document.documentElement.clientHeight,
425
+ };
426
+ }
427
+ return {
428
+ width: this.windowRef.native.innerWidth,
429
+ height: this.windowRef.native.innerHeight,
430
+ };
431
+ }
432
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.9", ngImport: i0, type: ViewportService, deps: [{ token: WindowRef }, { token: ViewportServerSizeService }], target: i0.ɵɵFactoryTarget.Injectable });
433
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.2.9", ngImport: i0, type: ViewportService, providedIn: "root" });
434
+ }
435
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.9", ngImport: i0, type: ViewportService, decorators: [{
436
+ type: Injectable,
437
+ args: [{
438
+ providedIn: "root",
439
+ }]
440
+ }], ctorParameters: () => [{ type: WindowRef }, { type: ViewportServerSizeService }] });
441
+
442
+ /**
443
+ * Utility function to generate rules based on strategies.
444
+ *
445
+ * @param dataConfig Data config to generate rules based on.
446
+ * @param strategy Strategy to use when building rules.
447
+ * @param sizeTypes Available size types ordered by index type. (Can be obtained from `ViewportService`)
448
+ * @param sizeTypeMap Available size type map. (Can be obtained from `ViewportService`)
449
+ * @returns Returns a collection of rules (ordered).
450
+ */
451
+ function generateViewportRulesRangeFromDataMatcher(dataConfig, strategy, sizeTypes, sizeTypeMap) {
452
+ const ruleBuilderFn = matchStrategyHandlerMap[strategy];
453
+ if (!ruleBuilderFn) {
454
+ throw Error(`generateViewportRulesRangeFromDataMatcher: Viewport Data strategy not implemented. Strategy: '${strategy}'`);
455
+ }
456
+ let dataSizes = [];
457
+ for (const key in dataConfig) {
458
+ if (Object.prototype.hasOwnProperty.call(dataConfig, key)) {
459
+ const data = dataConfig[key];
460
+ if (data === undefined) {
461
+ continue;
462
+ }
463
+ const size = sizeTypeMap[key];
464
+ if (size) {
465
+ dataSizes.push(size);
466
+ }
467
+ }
468
+ }
469
+ dataSizes = dataSizes.sort(({ type: typeA }, { type: typeB }) => typeA - typeB);
470
+ const rules = [];
471
+ if (dataConfig.default) {
472
+ rules.push({ value: dataConfig.default, min: undefined, max: undefined });
473
+ }
474
+ let prevRule;
475
+ for (let index = 0; index < dataSizes.length; index++) {
476
+ const prevDataSize = dataSizes[index - 1];
477
+ const nextDataSize = dataSizes[index + 1];
478
+ const dataSize = dataSizes[index];
479
+ const prevSize = sizeTypes[dataSize.type - 1];
480
+ // const nextSize = sizeTypes[dataSize.type + 1];
481
+ const data = dataConfig[dataSize.name];
482
+ const rule = {
483
+ value: data,
484
+ min: undefined,
485
+ max: undefined,
486
+ };
487
+ ruleBuilderFn(rule, dataSize, nextDataSize, prevDataSize, prevSize, prevRule, sizeTypes);
488
+ prevRule = rule;
489
+ rules.push(rule);
490
+ }
491
+ return rules;
492
+ }
493
+ const matchStrategyHandlerMap = {
494
+ [ViewportDataMatchStrategy.exact]: (rule, dataSize, _nextDataSize, _prevDataSize, prevSize) => {
495
+ rule.max = dataSize.widthThreshold;
496
+ if (prevSize) {
497
+ rule.min = prevSize.widthThreshold + 1;
498
+ }
499
+ },
500
+ [ViewportDataMatchStrategy.smaller]: (rule, dataSize, nextDataSize, _prevDataSize, prevSize) => {
501
+ if (nextDataSize) {
502
+ rule.max = dataSize.widthThreshold;
503
+ }
504
+ if (prevSize) {
505
+ rule.min = prevSize.widthThreshold + 1;
506
+ }
507
+ },
508
+ [ViewportDataMatchStrategy.larger]: (rule, dataSize, _nextDataSize, prevDataSize) => {
509
+ if (dataSize) {
510
+ rule.max = dataSize.widthThreshold;
511
+ }
512
+ if (prevDataSize) {
513
+ rule.min = prevDataSize.widthThreshold + 1;
514
+ }
515
+ },
516
+ [ViewportDataMatchStrategy.closestSmallerFirst]: (rule, dataSize, nextDataSize, _prevDataSize, _prevSize, prevRule, sizeTypes) => {
517
+ if (nextDataSize) {
518
+ rule.max = calculateClosestWidthThreshold(nextDataSize, dataSize, sizeTypes, true);
519
+ }
520
+ if (prevRule?.max) {
521
+ rule.min = prevRule.max + 1;
522
+ }
523
+ },
524
+ [ViewportDataMatchStrategy.closestLargerFirst]: (rule, dataSize, nextDataSize, _prevDataSize, _prevSize, prevRule, sizeTypes) => {
525
+ if (nextDataSize) {
526
+ rule.max = calculateClosestWidthThreshold(nextDataSize, dataSize, sizeTypes, false);
527
+ }
528
+ if (prevRule?.max) {
529
+ rule.min = prevRule.max + 1;
530
+ }
531
+ },
532
+ };
533
+ function calculateClosestWidthThreshold(nextDataSize, dataSize, sizeTypes, isSmallerPreferred) {
534
+ const fn = isSmallerPreferred ? Math.ceil : Math.floor;
535
+ // get closest between curr and next
536
+ const diffIndex = fn((nextDataSize.type - dataSize.type - 1) / 2);
537
+ const diffNextSize = sizeTypes[dataSize.type + diffIndex];
538
+ return (diffNextSize || dataSize).widthThreshold;
539
+ }
540
+
541
+ class ViewportDataService {
542
+ viewport = inject(ViewportService);
543
+ config = inject(VIEWPORT_OPTIONS);
544
+ /** Get data for match. */
545
+ get(dataConfig, strategy = this.config.defaultDataMatchStrategy, sizeType = this.viewport.sizeTypeSnapshot) {
546
+ return matchViewportData(dataConfig, sizeType, strategy, this.viewport.sizeTypes, this.viewport.sizeTypeMap);
547
+ }
548
+ /** Get data for match as observable. */
549
+ get$(dataConfig, strategy, throttle = true) {
550
+ return (throttle ? this.viewport.sizeType$ : this.viewport.sizeTypeSnap$).pipe(map(sizeType => this.get(dataConfig, strategy, sizeType)), distinctUntilChanged());
551
+ }
552
+ /** Generate rules based on strategies for data. */
553
+ generateRules(dataConfig, strategy = this.config.defaultDataMatchStrategy) {
554
+ return generateViewportRulesRangeFromDataMatcher(dataConfig, strategy, this.viewport.sizeTypes, this.viewport.sizeTypeMap);
555
+ }
556
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.9", ngImport: i0, type: ViewportDataService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
557
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.2.9", ngImport: i0, type: ViewportDataService, providedIn: "root" });
558
+ }
559
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.9", ngImport: i0, type: ViewportDataService, decorators: [{
560
+ type: Injectable,
561
+ args: [{
562
+ providedIn: "root",
563
+ }]
564
+ }] });
565
+
566
+ class ViewportDataPipe {
567
+ viewportData;
568
+ cdr;
569
+ markForTransform = true;
570
+ value;
571
+ data;
572
+ strategy;
573
+ data$$ = Subscription.EMPTY;
574
+ constructor(viewportData, cdr) {
575
+ this.viewportData = viewportData;
576
+ this.cdr = cdr;
577
+ }
578
+ transform(data, strategy) {
579
+ if (!this.markForTransform && data === this.data && strategy === this.strategy) {
580
+ return this.value;
581
+ }
582
+ this.data = data;
583
+ this.strategy = strategy;
584
+ this.data$$.unsubscribe();
585
+ this.data$$ = this.viewportData.get$(data, ViewportDataMatchStrategy[strategy]).pipe(tap(value => {
586
+ this.markForTransform = true;
587
+ this.value = value;
588
+ this.cdr.markForCheck();
589
+ })).subscribe();
590
+ this.markForTransform = false;
591
+ return this.value;
592
+ }
593
+ ngOnDestroy() {
594
+ this.data$$.unsubscribe();
595
+ }
596
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.9", ngImport: i0, type: ViewportDataPipe, deps: [{ token: ViewportDataService }, { token: i0.ChangeDetectorRef }], target: i0.ɵɵFactoryTarget.Pipe });
597
+ static ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "18.2.9", ngImport: i0, type: ViewportDataPipe, isStandalone: true, name: "ssvViewportData", pure: false });
598
+ }
599
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.9", ngImport: i0, type: ViewportDataPipe, decorators: [{
600
+ type: Pipe,
601
+ args: [{
602
+ name: "ssvViewportData",
603
+ pure: false,
604
+ standalone: true,
605
+ }]
606
+ }], ctorParameters: () => [{ type: ViewportDataService }, { type: i0.ChangeDetectorRef }] });
607
+
608
+ const NAME_CAMEL = "ssvViewportMatcherVar";
609
+ class SsvViewportMatcherVarContext {
610
+ $implicit;
611
+ constructor($implicit = false) {
612
+ this.$implicit = $implicit;
613
+ }
614
+ }
615
+ class SsvViewportMatcherVarDirective {
616
+ viewport;
617
+ viewContainer;
618
+ templateRef;
619
+ _matchConditions = {};
620
+ _context = new SsvViewportMatcherVarContext();
621
+ _destroy$ = new Subject();
622
+ _update$ = new ReplaySubject(1);
623
+ _viewRef;
624
+ set condition(value) {
625
+ if (isViewportSizeMatcherExpression(value)) {
626
+ this._matchConditions.expression = value;
627
+ }
628
+ else if (isViewportSizeMatcherTupleExpression(value)) {
629
+ const [op, size] = value;
630
+ this._matchConditions.expression = {
631
+ operation: op,
632
+ size
633
+ };
634
+ }
635
+ else {
636
+ this._matchConditions.sizeType = value;
637
+ }
638
+ this._update$.next();
639
+ }
640
+ constructor(viewport, viewContainer, templateRef) {
641
+ this.viewport = viewport;
642
+ this.viewContainer = viewContainer;
643
+ this.templateRef = templateRef;
644
+ }
645
+ ngOnInit() {
646
+ this.updateView();
647
+ combineLatest([this.viewport.sizeType$, this._update$]).pipe(map(([sizeType]) => isViewportConditionMatch(sizeType, this._matchConditions, this.viewport.sizeTypeMap)), tap(x => this._context.$implicit = x), tap(() => this._viewRef.markForCheck()), takeUntil(this._destroy$)).subscribe();
648
+ }
649
+ ngOnDestroy() {
650
+ this._destroy$.next();
651
+ this._destroy$.complete();
652
+ }
653
+ updateView() {
654
+ this.viewContainer.clear();
655
+ this._viewRef = this.viewContainer.createEmbeddedView(this.templateRef, this._context);
656
+ }
657
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.9", ngImport: i0, type: SsvViewportMatcherVarDirective, deps: [{ token: ViewportService }, { token: i0.ViewContainerRef }, { token: i0.TemplateRef }], target: i0.ɵɵFactoryTarget.Directive });
658
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "18.2.9", type: SsvViewportMatcherVarDirective, isStandalone: true, selector: "[ssvViewportMatcherVar]", inputs: { condition: ["ssvViewportMatcherVarWhen", "condition"] }, ngImport: i0 });
659
+ }
660
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.9", ngImport: i0, type: SsvViewportMatcherVarDirective, decorators: [{
661
+ type: Directive,
662
+ args: [{
663
+ selector: `[${NAME_CAMEL}]`,
664
+ standalone: true,
665
+ }]
666
+ }], ctorParameters: () => [{ type: ViewportService }, { type: i0.ViewContainerRef }, { type: i0.TemplateRef }], propDecorators: { condition: [{
667
+ type: Input,
668
+ args: [`${NAME_CAMEL}When`]
669
+ }] } });
670
+
671
+ class SsvViewportMatcherContext {
672
+ sizeType = null;
673
+ sizeTypeExclude = null;
674
+ expression;
675
+ }
676
+ class SsvViewportMatcherDirective {
677
+ viewport;
678
+ renderer;
679
+ viewContainer;
680
+ cdr;
681
+ sizeInfo;
682
+ _context = new SsvViewportMatcherContext();
683
+ _thenTemplateRef = null;
684
+ _elseTemplateRef = null;
685
+ _thenViewRef = null;
686
+ _elseViewRef = null;
687
+ sizeType$$ = Subscription.EMPTY;
688
+ cssClass$$ = Subscription.EMPTY;
689
+ _update$ = new Subject();
690
+ set ssvViewportMatcher(value) {
691
+ if (isViewportSizeMatcherExpression(value)) {
692
+ this._context.expression = value;
693
+ }
694
+ else if (isViewportSizeMatcherTupleExpression(value)) {
695
+ const [op, size] = value;
696
+ this._context.expression = {
697
+ operation: op,
698
+ size
699
+ };
700
+ }
701
+ else {
702
+ this._context.sizeType = value;
703
+ }
704
+ if (this.sizeInfo) {
705
+ this._update$.next(this._context);
706
+ }
707
+ }
708
+ set ssvViewportMatcherExclude(value) {
709
+ this._context.sizeTypeExclude = value;
710
+ if (this.sizeInfo) {
711
+ this._update$.next(this._context);
712
+ }
713
+ }
714
+ set ssvViewportMatcherElse(templateRef) {
715
+ this._elseTemplateRef = templateRef;
716
+ this._elseViewRef = null; // clear previous view if any.
717
+ if (this.sizeInfo) {
718
+ this._update$.next(this._context);
719
+ }
720
+ }
721
+ constructor(viewport, renderer, viewContainer, cdr, templateRef) {
722
+ this.viewport = viewport;
723
+ this.renderer = renderer;
724
+ this.viewContainer = viewContainer;
725
+ this.cdr = cdr;
726
+ this._thenTemplateRef = templateRef;
727
+ }
728
+ ngOnInit() {
729
+ // console.log("ssvViewportMatcher init");
730
+ this._update$
731
+ .pipe(
732
+ // tap(x => console.log(">>> ssvViewportMatcher - update triggered", x)),
733
+ filter(() => !!this.sizeInfo),
734
+ // tap(x => console.log(">>> ssvViewportMatcher - updating...", x)),
735
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
736
+ tap(() => this._updateView(this.sizeInfo)), tap(() => this.cdr.markForCheck()))
737
+ .subscribe();
738
+ this.sizeType$$ = this.viewport.sizeType$
739
+ .pipe(
740
+ // tap(x => console.log("ssvViewportMatcher - sizeType changed", x)),
741
+ tap(x => this.sizeInfo = x), tap(() => this._update$.next(this._context)))
742
+ .subscribe();
743
+ this.cssClass$$ = this.viewport.sizeType$
744
+ .pipe(startWith(undefined), filter(() => !!(this._thenViewRef || this._elseViewRef)), pairwise(), tap(([prev, curr]) => {
745
+ const el = this._thenViewRef
746
+ ? this._thenViewRef.rootNodes[0]
747
+ : this._elseViewRef?.rootNodes[0];
748
+ if (!el.classList) {
749
+ return;
750
+ }
751
+ if (prev) {
752
+ this.renderer.removeClass(el, `ssv-vp-size--${prev.name}`);
753
+ }
754
+ this.renderer.addClass(el, `ssv-vp-size--${curr?.name}`);
755
+ }))
756
+ .subscribe();
757
+ }
758
+ ngOnDestroy() {
759
+ this.cssClass$$.unsubscribe();
760
+ this.sizeType$$.unsubscribe();
761
+ this._update$.complete();
762
+ }
763
+ _updateView(sizeInfo) {
764
+ if (isViewportConditionMatch(sizeInfo, this._context, this.viewport.sizeTypeMap)) {
765
+ if (!this._thenViewRef) {
766
+ this.viewContainer.clear();
767
+ this._elseViewRef = null;
768
+ if (this._thenTemplateRef) {
769
+ this._thenViewRef = this.viewContainer.createEmbeddedView(this._thenTemplateRef, this._context);
770
+ }
771
+ }
772
+ }
773
+ else {
774
+ if (!this._elseViewRef) {
775
+ this.viewContainer.clear();
776
+ this._thenViewRef = null;
777
+ if (this._elseTemplateRef) {
778
+ this._elseViewRef = this.viewContainer.createEmbeddedView(this._elseTemplateRef, this._context);
779
+ }
780
+ }
781
+ }
782
+ }
783
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.9", ngImport: i0, type: SsvViewportMatcherDirective, deps: [{ token: ViewportService }, { token: i0.Renderer2 }, { token: i0.ViewContainerRef }, { token: i0.ChangeDetectorRef }, { token: i0.TemplateRef }], target: i0.ɵɵFactoryTarget.Directive });
784
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "18.2.9", type: SsvViewportMatcherDirective, isStandalone: true, selector: "[ssvViewportMatcher]", inputs: { ssvViewportMatcher: "ssvViewportMatcher", ssvViewportMatcherExclude: "ssvViewportMatcherExclude", ssvViewportMatcherElse: "ssvViewportMatcherElse" }, exportAs: ["ssvViewportMatcher"], ngImport: i0 });
785
+ }
786
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.9", ngImport: i0, type: SsvViewportMatcherDirective, decorators: [{
787
+ type: Directive,
788
+ args: [{
789
+ selector: "[ssvViewportMatcher]",
790
+ exportAs: "ssvViewportMatcher",
791
+ standalone: true,
792
+ }]
793
+ }], ctorParameters: () => [{ type: ViewportService }, { type: i0.Renderer2 }, { type: i0.ViewContainerRef }, { type: i0.ChangeDetectorRef }, { type: i0.TemplateRef }], propDecorators: { ssvViewportMatcher: [{
794
+ type: Input
795
+ }], ssvViewportMatcherExclude: [{
796
+ type: Input
797
+ }], ssvViewportMatcherElse: [{
798
+ type: Input
799
+ }] } });
800
+
801
+ const EXPORTED_IMPORTS$1 = [
802
+ SsvViewportMatcherDirective,
803
+ SsvViewportMatcherVarDirective,
804
+ ViewportDataPipe,
805
+ ];
806
+ class SsvUxViewportModule {
807
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.9", ngImport: i0, type: SsvUxViewportModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
808
+ static ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "18.2.9", ngImport: i0, type: SsvUxViewportModule, imports: [SsvViewportMatcherDirective,
809
+ SsvViewportMatcherVarDirective,
810
+ ViewportDataPipe], exports: [SsvViewportMatcherDirective,
811
+ SsvViewportMatcherVarDirective,
812
+ ViewportDataPipe] });
813
+ static ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "18.2.9", ngImport: i0, type: SsvUxViewportModule });
814
+ }
815
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.9", ngImport: i0, type: SsvUxViewportModule, decorators: [{
816
+ type: NgModule,
817
+ args: [{
818
+ imports: [EXPORTED_IMPORTS$1],
819
+ exports: [EXPORTED_IMPORTS$1]
820
+ }]
821
+ }] });
822
+
823
+ const EXPORTED_IMPORTS = [
824
+ SsvUxViewportModule,
825
+ ];
826
+ class SsvUxModule {
827
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.9", ngImport: i0, type: SsvUxModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
828
+ static ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "18.2.9", ngImport: i0, type: SsvUxModule, imports: [SsvUxViewportModule], exports: [SsvUxViewportModule] });
829
+ static ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "18.2.9", ngImport: i0, type: SsvUxModule, imports: [EXPORTED_IMPORTS, SsvUxViewportModule] });
830
+ }
831
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.9", ngImport: i0, type: SsvUxModule, decorators: [{
832
+ type: NgModule,
833
+ args: [{
834
+ imports: [EXPORTED_IMPORTS],
835
+ exports: [EXPORTED_IMPORTS]
836
+ }]
837
+ }] });
838
+
839
+ const VERSION = "3.0.0-dev.38";
840
+
841
+ /**
842
+ * Generated bundle index. Do not edit.
843
+ */
844
+
845
+ export { ComparisonOperation, DeviceType, SsvUxModule, SsvUxViewportModule, SsvViewportMatcherContext, SsvViewportMatcherDirective, SsvViewportMatcherVarContext, SsvViewportMatcherVarDirective, VERSION, VIEWPORT_OPTIONS, VIEWPORT_SSR_DEVICE, ViewportDataMatchStrategy, ViewportDataPipe, ViewportDataService, ViewportServerSizeService, ViewportService, ViewportSizeType, generateViewportSizeType, provideSsvUxViewportOptions, withViewportSsrDevice };
846
+ //# sourceMappingURL=ssv-ngx.ux.mjs.map