@ssv/ngx.ux 2.1.2-dev.37 → 3.0.0-dev.32

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 (79) hide show
  1. package/README.md +28 -114
  2. package/eslint.config.js +43 -0
  3. package/index.ts +1 -0
  4. package/jest.config.ts +21 -0
  5. package/ng-package.json +7 -0
  6. package/package.json +4 -30
  7. package/project.json +36 -0
  8. package/src/index.ts +4 -0
  9. package/src/internal/internal.model.ts +3 -0
  10. package/src/platform/window.ts +31 -0
  11. package/src/test-setup.ts +8 -0
  12. package/src/ux.module.ts +15 -0
  13. package/src/version.ts +1 -0
  14. package/src/viewport/index.ts +20 -0
  15. package/src/viewport/viewport-data/README.md +47 -0
  16. package/src/viewport/viewport-data/index.ts +3 -0
  17. package/src/viewport/viewport-data/viewport-data-matcher.spec.ts +227 -0
  18. package/src/viewport/viewport-data/viewport-data-matcher.ts +175 -0
  19. package/src/viewport/viewport-data/viewport-data.pipe.ts +51 -0
  20. package/src/viewport/viewport-data/viewport-data.service.ts +48 -0
  21. package/src/viewport/viewport-data/viewport-data.utils.spec.ts +228 -0
  22. package/src/viewport/viewport-data/viewport-data.utils.ts +137 -0
  23. package/src/viewport/viewport-matcher-var.directive.ts +85 -0
  24. package/src/viewport/viewport-matcher.directive.ts +170 -0
  25. package/src/viewport/viewport-server-size.service.ts +37 -0
  26. package/src/viewport/viewport.model.ts +54 -0
  27. package/src/viewport/viewport.module.ts +19 -0
  28. package/src/viewport/viewport.options.ts +74 -0
  29. package/src/viewport/viewport.service.ts +123 -0
  30. package/src/viewport/viewport.util.spec.ts +254 -0
  31. package/src/viewport/viewport.util.ts +152 -0
  32. package/tsconfig.json +28 -0
  33. package/tsconfig.lib.json +12 -0
  34. package/tsconfig.lib.prod.json +9 -0
  35. package/tsconfig.spec.json +11 -0
  36. package/LICENSE +0 -21
  37. package/config.d.ts +0 -7
  38. package/esm2020/config.mjs +0 -7
  39. package/esm2020/index.mjs +0 -5
  40. package/esm2020/internal/internal.model.mjs +0 -2
  41. package/esm2020/module.mjs +0 -65
  42. package/esm2020/platform/window.mjs +0 -30
  43. package/esm2020/ssv-ngx.ux.mjs +0 -5
  44. package/esm2020/version.mjs +0 -2
  45. package/esm2020/viewport/index.mjs +0 -9
  46. package/esm2020/viewport/viewport-data/index.mjs +0 -4
  47. package/esm2020/viewport/viewport-data/viewport-data-matcher.mjs +0 -108
  48. package/esm2020/viewport/viewport-data/viewport-data.pipe.mjs +0 -43
  49. package/esm2020/viewport/viewport-data/viewport-data.service.mjs +0 -37
  50. package/esm2020/viewport/viewport-data/viewport-data.utils.mjs +0 -100
  51. package/esm2020/viewport/viewport-matcher-var.directive.mjs +0 -63
  52. package/esm2020/viewport/viewport-matcher.directive.mjs +0 -131
  53. package/esm2020/viewport/viewport-server-size.service.mjs +0 -43
  54. package/esm2020/viewport/viewport.const.mjs +0 -17
  55. package/esm2020/viewport/viewport.model.mjs +0 -30
  56. package/esm2020/viewport/viewport.service.mjs +0 -66
  57. package/esm2020/viewport/viewport.util.mjs +0 -117
  58. package/fesm2015/ssv-ngx.ux.mjs +0 -826
  59. package/fesm2015/ssv-ngx.ux.mjs.map +0 -1
  60. package/fesm2020/ssv-ngx.ux.mjs +0 -820
  61. package/fesm2020/ssv-ngx.ux.mjs.map +0 -1
  62. package/index.d.ts +0 -4
  63. package/internal/internal.model.d.ts +0 -9
  64. package/module.d.ts +0 -19
  65. package/platform/window.d.ts +0 -13
  66. package/version.d.ts +0 -1
  67. package/viewport/index.d.ts +0 -8
  68. package/viewport/viewport-data/index.d.ts +0 -3
  69. package/viewport/viewport-data/viewport-data-matcher.d.ts +0 -32
  70. package/viewport/viewport-data/viewport-data.pipe.d.ts +0 -18
  71. package/viewport/viewport-data/viewport-data.service.d.ts +0 -20
  72. package/viewport/viewport-data/viewport-data.utils.d.ts +0 -21
  73. package/viewport/viewport-matcher-var.directive.d.ts +0 -25
  74. package/viewport/viewport-matcher.directive.d.ts +0 -33
  75. package/viewport/viewport-server-size.service.d.ts +0 -12
  76. package/viewport/viewport.const.d.ts +0 -5
  77. package/viewport/viewport.model.d.ts +0 -59
  78. package/viewport/viewport.service.d.ts +0 -37
  79. package/viewport/viewport.util.d.ts +0 -25
@@ -0,0 +1,19 @@
1
+ import { NgModule } from "@angular/core";
2
+
3
+ import { ViewportDataPipe } from "./viewport-data/viewport-data.pipe";
4
+ import { SsvViewportMatcherVarDirective } from "./viewport-matcher-var.directive";
5
+ import { SsvViewportMatcherDirective } from "./viewport-matcher.directive";
6
+
7
+ const EXPORTED_IMPORTS = [
8
+ SsvViewportMatcherDirective,
9
+ SsvViewportMatcherVarDirective,
10
+ ViewportDataPipe,
11
+ ];
12
+
13
+ @NgModule({
14
+ imports: [EXPORTED_IMPORTS],
15
+ exports: [EXPORTED_IMPORTS]
16
+ })
17
+ export class SsvUxViewportModule {
18
+
19
+ }
@@ -0,0 +1,74 @@
1
+ import { type EnvironmentProviders, InjectionToken, makeEnvironmentProviders } from "@angular/core";
2
+
3
+ import type { Dictionary } from "../internal/internal.model";
4
+ import { ViewportDataMatchStrategy } from "./viewport-data/viewport-data-matcher";
5
+ import { VIEWPORT_SSR_DEVICE } from "./viewport-server-size.service";
6
+ import { DeviceType } from "./viewport.model";
7
+
8
+ /** Default viewport breakpoints. */
9
+ export const UX_VIEWPORT_DEFAULT_BREAKPOINTS: Dictionary<number> = {
10
+ xsmall: 450,
11
+ small: 767,
12
+ medium: 992,
13
+ large: 1200,
14
+ xlarge: 1500,
15
+ xxlarge: 1920,
16
+ xxlarge1: 2100,
17
+ };
18
+
19
+ export interface UxViewportOptions {
20
+ /** Polling speed on resizing (in milliseconds). e.g. the higher the number the longer it takes to recalculate. */
21
+ resizePollingSpeed: number;
22
+
23
+ /** Breakpoints to use. Key needs to match the size type and the value the width threshold.
24
+ * e.g. given width '1000' and `medium` is set to '992' => `large`.
25
+ */
26
+ breakpoints: Dictionary<number>;
27
+
28
+ /** Default data match strategy to use. */
29
+ defaultDataMatchStrategy: ViewportDataMatchStrategy;
30
+ }
31
+
32
+ const DEFAULT_OPTIONS = Object.freeze<UxViewportOptions>({
33
+ resizePollingSpeed: 33,
34
+ breakpoints: UX_VIEWPORT_DEFAULT_BREAKPOINTS,
35
+ defaultDataMatchStrategy: ViewportDataMatchStrategy.smaller,
36
+ });
37
+
38
+ export const VIEWPORT_OPTIONS = new InjectionToken<UxViewportOptions>("SSV_UX_VIEWPORT_OPTIONS", {
39
+ factory: () => DEFAULT_OPTIONS,
40
+ });
41
+
42
+ export function provideSsvUxViewportOptions(
43
+ options: Partial<UxViewportOptions> | ((defaults: Readonly<UxViewportOptions>) => Partial<UxViewportOptions>),
44
+ ...features: EnvironmentProviders[]
45
+ ): EnvironmentProviders {
46
+ return makeEnvironmentProviders([
47
+ {
48
+ provide: VIEWPORT_OPTIONS,
49
+ useFactory: () => {
50
+ let opts = typeof options === "function" ? options(DEFAULT_OPTIONS) : options;
51
+ opts = opts
52
+ ? {
53
+ ...DEFAULT_OPTIONS,
54
+ ...opts, // NOTE: breakpoints shoudn't be merged
55
+ }
56
+ : DEFAULT_OPTIONS;
57
+ return opts;
58
+ },
59
+ },
60
+ ...features,
61
+ ]);
62
+ }
63
+
64
+ export function withViewportSsrDevice(options: DeviceType | (() => DeviceType)): EnvironmentProviders {
65
+ return makeEnvironmentProviders([
66
+ {
67
+ provide: VIEWPORT_SSR_DEVICE,
68
+ useFactory: () => {
69
+ const deviceType = typeof options === "function" ? options() : options;
70
+ return deviceType;
71
+ },
72
+ },
73
+ ]);
74
+ }
@@ -0,0 +1,123 @@
1
+ import { Injectable, inject } from "@angular/core";
2
+ import {
3
+ Observable,
4
+ fromEvent,
5
+ of,
6
+ map,
7
+ tap,
8
+ distinctUntilChanged,
9
+ startWith,
10
+ share,
11
+ shareReplay,
12
+ auditTime,
13
+ } from "rxjs";
14
+
15
+ import { ViewportSizeTypeInfo, ViewportSize } from "./viewport.model";
16
+ import { WindowRef } from "../platform/window";
17
+ import { ViewportServerSizeService } from "./viewport-server-size.service";
18
+ import { generateViewportSizeTypeInfoList, generateViewportSizeTypeInfoRefs, getSizeTypeInfo } from "./viewport.util";
19
+ import type { Dictionary } from "../internal/internal.model";
20
+ import { VIEWPORT_OPTIONS } from "./viewport.options";
21
+
22
+ @Injectable({
23
+ providedIn: "root",
24
+ })
25
+ export class ViewportService {
26
+
27
+ /** Window resize observable. */
28
+ readonly resizeSnap$: Observable<ViewportSize>;
29
+
30
+ /** Window resize observable (which is also throttled). */
31
+ readonly resize$: Observable<ViewportSize>;
32
+
33
+ /** Viewport size type observable (which is also throttled). */
34
+ readonly sizeType$: Observable<ViewportSizeTypeInfo>;
35
+
36
+ /** Viewport size type observable. */
37
+ readonly sizeTypeSnap$: Observable<ViewportSizeTypeInfo>;
38
+
39
+ /** Viewport size type snapshot of the last value. (Prefer use `sizeType$` observable when possible.) */
40
+ get sizeTypeSnapshot(): ViewportSizeTypeInfo { return this._sizeTypeSnapshot; }
41
+
42
+ /** Viewport size observable (which is also throttled). */
43
+ readonly size$: Observable<ViewportSize>;
44
+
45
+ /** Viewport size observable. */
46
+ readonly sizeSnap$: Observable<ViewportSize>;
47
+
48
+ /** Size types refs of the generated viewport size type info. */
49
+ get sizeTypeMap(): Dictionary<ViewportSizeTypeInfo> { return this._sizeTypeMap; }
50
+
51
+ /** Viewport size types list ordered by type, smallest to largest. */
52
+ get sizeTypes(): ViewportSizeTypeInfo[] { return this._sizeTypes; }
53
+
54
+ private _sizeTypeMap: Dictionary<ViewportSizeTypeInfo>;
55
+ private _sizeTypes: ViewportSizeTypeInfo[];
56
+ private _sizeTypeSnapshot: ViewportSizeTypeInfo;
57
+
58
+ constructor(
59
+ private windowRef: WindowRef,
60
+ private viewportServerSize: ViewportServerSizeService,
61
+ ) {
62
+ const config = inject(VIEWPORT_OPTIONS);
63
+ this._sizeTypes = generateViewportSizeTypeInfoList(config.breakpoints);
64
+ this._sizeTypeMap = generateViewportSizeTypeInfoRefs(this._sizeTypes);
65
+
66
+ if (windowRef.hasNative) {
67
+ this.resizeSnap$ = fromEvent<Event>(window, "resize").pipe(
68
+ map(() => this.getViewportSize()),
69
+ share()
70
+ );
71
+
72
+ this.resize$ = this.resizeSnap$.pipe(
73
+ auditTime(config.resizePollingSpeed),
74
+ share(),
75
+ );
76
+ } else {
77
+ this.resizeSnap$ = this.resize$ = of(viewportServerSize.get());
78
+ }
79
+ const size = this.getViewportSize();
80
+ this._sizeTypeSnapshot = getSizeTypeInfo(size.width, this.sizeTypes);
81
+
82
+ const sizeFn = (obs$: Observable<ViewportSize>) => obs$.pipe(
83
+ startWith(size),
84
+ distinctUntilChanged((a, b) => a.width === b.width && a.height === b.height),
85
+ shareReplay(1),
86
+ );
87
+
88
+ this.sizeSnap$ = sizeFn(this.resizeSnap$);
89
+ this.size$ = sizeFn(this.resize$);
90
+
91
+ const sizeTypeFn = (obs$: Observable<ViewportSize>) => obs$.pipe(
92
+ distinctUntilChanged((a, b) => a.width === b.width),
93
+ map(x => getSizeTypeInfo(x.width, this.sizeTypes)),
94
+ distinctUntilChanged(),
95
+ tap(x => this._sizeTypeSnapshot = x),
96
+ shareReplay(1),
97
+ );
98
+
99
+ this.sizeType$ = sizeTypeFn(this.size$);
100
+ this.sizeTypeSnap$ = sizeTypeFn(this.sizeSnap$);
101
+ }
102
+
103
+ /** Returns the current viewport size */
104
+ private getViewportSize(): ViewportSize {
105
+ if (!this.windowRef.hasNative) {
106
+ return this.viewportServerSize.get();
107
+ }
108
+
109
+ const ua = navigator.userAgent.toLowerCase();
110
+ if (ua.indexOf("safari") !== -1 && ua.indexOf("chrome") === -1) { // safari subtracts the scrollbar width
111
+ return {
112
+ width: this.windowRef.native.document.documentElement.clientWidth,
113
+ height: this.windowRef.native.document.documentElement.clientHeight,
114
+ };
115
+ }
116
+
117
+ return {
118
+ width: this.windowRef.native.innerWidth,
119
+ height: this.windowRef.native.innerHeight,
120
+ };
121
+ }
122
+
123
+ }
@@ -0,0 +1,254 @@
1
+ import {
2
+ isViewportConditionMatch as isViewportConditionMatch_,
3
+ generateViewportSizeTypeInfoList,
4
+ generateViewportSizeTypeInfoRefs,
5
+ generateViewportSizeType,
6
+ getSizeTypeInfo,
7
+ } from "./viewport.util";
8
+ import { ComparisonOperation, ViewportMatchConditions, ViewportSizeType, ViewportSizeTypeInfo } from "./viewport.model";
9
+ import { UX_VIEWPORT_DEFAULT_BREAKPOINTS } from "./viewport.options";
10
+
11
+ export enum TestViewportSizeType {
12
+ xsmall = 0,
13
+ small = 1,
14
+ medium = 2,
15
+ large = 3,
16
+ hd = 4,
17
+ fullHd = 5,
18
+ }
19
+
20
+ const breakpoints: Record<keyof typeof TestViewportSizeType, number> = {
21
+ xsmall: 450,
22
+ small: 767,
23
+ medium: 992,
24
+ large: 1200,
25
+ hd: 1280,
26
+ fullHd: 1920,
27
+ };
28
+ const sizeTypes = generateViewportSizeTypeInfoList(breakpoints);
29
+ const sizeRefs = generateViewportSizeTypeInfoRefs(sizeTypes) as Record<keyof typeof TestViewportSizeType, ViewportSizeTypeInfo>;
30
+
31
+ const isViewportConditionMatch = (
32
+ evaluateSize: ViewportSizeTypeInfo,
33
+ conditions: ViewportMatchConditions
34
+ ) => isViewportConditionMatch_(evaluateSize, conditions, sizeRefs);
35
+
36
+ describe("Viewport utils", () => {
37
+
38
+ describe("given default breakpoints", () => {
39
+
40
+ it("should match ViewportSizeType definition", () => {
41
+ const result = Object.keys(UX_VIEWPORT_DEFAULT_BREAKPOINTS).reduce((r, key) => {
42
+ const idx = ViewportSizeType[key as unknown as ViewportSizeType];
43
+
44
+ expect(idx).not.toBeUndefined();
45
+ r.push(idx);
46
+ return r;
47
+ }, [] as string[]);
48
+
49
+ const viewportSizeTypeItems = Object.keys(ViewportSizeType).filter(v => isNaN(+v));
50
+ expect(viewportSizeTypeItems.length).toBe(result.length);
51
+ });
52
+
53
+ });
54
+
55
+
56
+ describe("generateViewportSizeType", () => {
57
+
58
+ describe("given custom breakpoints are used", () => {
59
+ const result = generateViewportSizeType(breakpoints);
60
+
61
+ it("should match the expected key/value pair", () => {
62
+ expect(result).toEqual(({
63
+ 0: "xsmall",
64
+ 1: "small",
65
+ 2: "medium",
66
+ 3: "large",
67
+ 4: "hd",
68
+ 5: "fullHd",
69
+ xsmall: 0,
70
+ small: 1,
71
+ medium: 2,
72
+ large: 3,
73
+ hd: 4,
74
+ fullHd: 5,
75
+ }));
76
+ });
77
+ });
78
+
79
+ });
80
+
81
+
82
+ describe("generateViewportSizeTypeInfoList", () => {
83
+
84
+ describe("given custom breakpoints are used", () => {
85
+ const result = generateViewportSizeTypeInfoList(breakpoints);
86
+
87
+ it("should match", () => {
88
+ expect(result).toEqual([
89
+ { name: "xsmall", type: 0, widthThreshold: 450 },
90
+ { name: "small", type: 1, widthThreshold: 767 },
91
+ { name: "medium", type: 2, widthThreshold: 992 },
92
+ { name: "large", type: 3, widthThreshold: 1200 },
93
+ { name: "hd", type: 4, widthThreshold: 1280 },
94
+ { name: "fullHd", type: 5, widthThreshold: 1920 }
95
+ ]);
96
+ });
97
+ });
98
+
99
+ });
100
+
101
+
102
+ describe("generateViewportSizeTypeInfoRefs", () => {
103
+
104
+ describe("given custom breakpoints are used", () => {
105
+ const sizes = generateViewportSizeTypeInfoList(breakpoints);
106
+ const result = generateViewportSizeTypeInfoRefs(sizes);
107
+
108
+ it("should match", () => {
109
+ expect(result).toEqual(
110
+ {
111
+ xsmall: { name: "xsmall", type: 0, widthThreshold: 450 },
112
+ small: { name: "small", type: 1, widthThreshold: 767 },
113
+ medium: { name: "medium", type: 2, widthThreshold: 992 },
114
+ large: { name: "large", type: 3, widthThreshold: 1200 },
115
+ hd: { name: "hd", type: 4, widthThreshold: 1280 },
116
+ fullHd: { name: "fullHd", type: 5, widthThreshold: 1920 },
117
+ 0: { name: "xsmall", type: 0, widthThreshold: 450 },
118
+ 1: { name: "small", type: 1, widthThreshold: 767 },
119
+ 2: { name: "medium", type: 2, widthThreshold: 992 },
120
+ 3: { name: "large", type: 3, widthThreshold: 1200 },
121
+ 4: { name: "hd", type: 4, widthThreshold: 1280 },
122
+ 5: { name: "fullHd", type: 5, widthThreshold: 1920 },
123
+ }
124
+ );
125
+ });
126
+ });
127
+
128
+ });
129
+
130
+ describe("getSizeTypeInfo", () => {
131
+ describe("when different widths are passed", () => {
132
+
133
+ it("should match", () => {
134
+ const dataSet = [
135
+ { width: 10, expectedSize: "xsmall" },
136
+ { width: 450, expectedSize: "xsmall" },
137
+ { width: 992, expectedSize: "medium" },
138
+ { width: 993, expectedSize: "large" },
139
+ { width: 1199, expectedSize: "large" },
140
+ { width: 1200, expectedSize: "large" },
141
+ { width: 1201, expectedSize: "hd" },
142
+ { width: 1280, expectedSize: "hd" },
143
+ { width: 1281, expectedSize: "fullHd" },
144
+ { width: 1920, expectedSize: "fullHd" },
145
+ { width: 2000, expectedSize: "fullHd" },
146
+ ];
147
+
148
+ for (const data of dataSet) {
149
+ const result = getSizeTypeInfo(data.width, sizeTypes);
150
+ expect(result.name).toBe(data.expectedSize);
151
+ }
152
+ });
153
+
154
+ });
155
+ });
156
+
157
+
158
+ describe("isViewportConditionMatch", () => {
159
+
160
+ describe("given only size include is used", () => {
161
+
162
+ describe("when single matching value is passed", () => {
163
+ const sizeType = "small";
164
+ const viewportRef = sizeRefs[sizeType];
165
+ const result = isViewportConditionMatch(viewportRef, { sizeType });
166
+
167
+ it("should return true", () => {
168
+ expect(result).toBe(true);
169
+ });
170
+ });
171
+
172
+ describe("when single not matching value is passed", () => {
173
+ const viewportRef = sizeRefs.small;
174
+ const result = isViewportConditionMatch(viewportRef, { sizeType: "xsmall" });
175
+
176
+ it("should return false", () => {
177
+ expect(result).toBe(false);
178
+ });
179
+ });
180
+
181
+ describe("when multi values are passed with a matching value", () => {
182
+ const viewportRef = sizeRefs.small;
183
+ const result = isViewportConditionMatch(viewportRef, {
184
+ sizeType: ["small", "medium"]
185
+ });
186
+
187
+ it("should return true", () => {
188
+ expect(result).toBe(true);
189
+ });
190
+ });
191
+
192
+ describe("when multi values are passed with a non matching value", () => {
193
+ const viewportRef = sizeRefs.large;
194
+ const result = isViewportConditionMatch(viewportRef, {
195
+ sizeType: ["small", "medium"]
196
+ });
197
+
198
+ it("should return false", () => {
199
+ expect(result).toBe(false);
200
+ });
201
+ });
202
+ });
203
+
204
+ describe("given expression tuple is used", () => {
205
+ describe("when different expressions are passed", () => {
206
+
207
+ it("should match", () => {
208
+ const dataSet = [
209
+ {
210
+ size: sizeRefs.small,
211
+ expression: { operation: ComparisonOperation.equals, size: "small" },
212
+ expectedResult: true
213
+ },
214
+ {
215
+ size: sizeRefs.xsmall,
216
+ expression: { operation: ComparisonOperation.notEquals, size: "small" },
217
+ expectedResult: true
218
+ },
219
+ {
220
+ size: sizeRefs.small,
221
+ expression: { operation: ComparisonOperation.notEquals, size: "small" },
222
+ expectedResult: false
223
+ },
224
+ {
225
+ size: sizeRefs.small,
226
+ expression: { operation: ComparisonOperation.lessThan, size: "medium" },
227
+ expectedResult: true
228
+ },
229
+ {
230
+ size: sizeRefs.medium,
231
+ expression: { operation: ComparisonOperation.lessThan, size: "medium" },
232
+ expectedResult: false
233
+ },
234
+ {
235
+ size: sizeRefs.medium,
236
+ expression: { operation: ComparisonOperation.lessOrEqualThan, size: "medium" },
237
+ expectedResult: true
238
+ },
239
+ ];
240
+
241
+ for (const data of dataSet) {
242
+ const result = isViewportConditionMatch(data.size, {
243
+ expression: data.expression
244
+ });
245
+ expect(result).toBe(data.expectedResult);
246
+ }
247
+ });
248
+
249
+ });
250
+ });
251
+
252
+ });
253
+
254
+ });
@@ -0,0 +1,152 @@
1
+ import { Dictionary } from "../internal/internal.model";
2
+ import {
3
+ ComparisonOperation,
4
+ ViewportSizeMatcherExpression,
5
+ ViewportSizeTypeInfo,
6
+ ViewportMatchConditions
7
+ } from "./viewport.model";
8
+
9
+ export function isViewportSizeMatcherExpression(value: unknown): value is ViewportSizeMatcherExpression {
10
+ if (typeof value !== "object" || !value) {
11
+ return false;
12
+ }
13
+ const args: Partial<ViewportSizeMatcherExpression> = value;
14
+ if (args.size && args.operation) {
15
+ return true;
16
+ }
17
+ return false;
18
+ }
19
+
20
+ export function isViewportSizeMatcherTupleExpression(arg: unknown): arg is [ComparisonOperation, string] {
21
+ if (!arg) {
22
+ return false;
23
+ }
24
+ if (Array.isArray(arg)) {
25
+ if (arg.length === 2) {
26
+ const [op] = arg;
27
+ return operations.includes(op);
28
+ }
29
+ }
30
+ return false;
31
+ }
32
+
33
+
34
+ const operations = Object.values(ComparisonOperation);
35
+
36
+ export const COMPARISON_OPERATION_FUNC_MAPPING: Dictionary<(a: number, b: number) => boolean> = {
37
+ [ComparisonOperation.equals]: (a: number, b: number) => a === b,
38
+ [ComparisonOperation.notEquals]: (a: number, b: number) => a !== b,
39
+ [ComparisonOperation.lessThan]: (a: number, b: number) => a < b,
40
+ [ComparisonOperation.lessOrEqualThan]: (a: number, b: number) => a <= b,
41
+ [ComparisonOperation.greaterThan]: (a: number, b: number) => a > b,
42
+ [ComparisonOperation.greaterOrEqualThan]: (a: number, b: number) => a >= b,
43
+ };
44
+
45
+ export function isViewportConditionMatch(
46
+ evaluateSize: ViewportSizeTypeInfo,
47
+ conditions: ViewportMatchConditions,
48
+ viewportSizeTypeInfoRefs: Dictionary<ViewportSizeTypeInfo>
49
+ ): boolean {
50
+ const isExcluded = match(conditions.sizeTypeExclude, evaluateSize.name, false);
51
+ let isIncluded;
52
+ let isExpressionTruthy;
53
+
54
+ if (!isExcluded && conditions.expression) {
55
+ const ref = viewportSizeTypeInfoRefs[conditions.expression.size];
56
+ if(!ref) {
57
+ throw new Error(`Viewport size type is invalid. Size type: '${conditions.expression.size}'`);
58
+ }
59
+ const expMatcher = COMPARISON_OPERATION_FUNC_MAPPING[conditions.expression.operation];
60
+
61
+ isExpressionTruthy = expMatcher(evaluateSize.type, ref.type);
62
+ } else {
63
+ isIncluded = match(conditions.sizeType, evaluateSize.name, true);
64
+ }
65
+
66
+ const shouldRender = (isExpressionTruthy || isIncluded) && !isExcluded;
67
+ // console.warn(">>> shouldRender", { evaluateSize, conditions, shouldRender });
68
+ return !!shouldRender;
69
+ }
70
+
71
+ function match(value: string | string[] | null | undefined, targetValue: string, defaultValue: boolean) {
72
+ if (!value) {
73
+ return defaultValue;
74
+ }
75
+
76
+ return Array.isArray(value)
77
+ ? value.includes(targetValue)
78
+ : value === targetValue;
79
+ }
80
+
81
+ export function getSizeTypeInfo(width: number, sizeTypes: ViewportSizeTypeInfo[]): ViewportSizeTypeInfo {
82
+ const lastEntryIndex = sizeTypes.length - 1;
83
+
84
+ for (let idx = 0; idx < lastEntryIndex; idx++) {
85
+ const viewportSizeTypeInfo = sizeTypes[idx];
86
+
87
+ if (width <= viewportSizeTypeInfo.widthThreshold) {
88
+ return viewportSizeTypeInfo;
89
+ }
90
+ }
91
+
92
+ return sizeTypes[lastEntryIndex];
93
+ }
94
+
95
+ /**
96
+ * Converts the breakpoints into a 2 dimensional array containing the name and width, and sorted from
97
+ * smallest to largest.
98
+ * @param breakpoints the breakpoints obtained from the config
99
+ * @internal
100
+ */
101
+ function getSortedBreakpoints(breakpoints: Dictionary<number>): [string, number][] {
102
+ return Object.entries(breakpoints)
103
+ .sort(([, widthA], [, widthB]) => widthA - widthB);
104
+ }
105
+
106
+ /**
107
+ * A util function which generates the ViewportSizeTypeInfo.type for each breakpoint.
108
+ * @param breakpoints the custom breakpoints
109
+ */
110
+ export function generateViewportSizeType<T extends Record<string, number>>(breakpoints: T): T & Record<number, string> {
111
+ return Object.freeze(
112
+ getSortedBreakpoints(breakpoints).reduce<Record<number | string, string | number>>(
113
+ (dictionary, [name], index) => {
114
+ dictionary[name] = index;
115
+ dictionary[index] = name;
116
+ return dictionary;
117
+ }, {}
118
+ )
119
+ ) as T & Record<number, string>;
120
+ }
121
+
122
+ /**
123
+ * Pre-processes the given breakpoints into an ordered list from smallest to largest while generating
124
+ * all the necessary information on the viewport.
125
+ * @param breakpoints the breakpoints obtained from the config
126
+ * @internal
127
+ */
128
+ export function generateViewportSizeTypeInfoList(breakpoints: Dictionary<number>): ViewportSizeTypeInfo[] {
129
+ return getSortedBreakpoints(breakpoints)
130
+ .map(([name, width], index) =>
131
+ (Object.freeze({
132
+ name,
133
+ type: index,
134
+ widthThreshold: width
135
+ }))
136
+ );
137
+ }
138
+
139
+ /**
140
+ * Converts the breakpoint list into a dictionary while using the name as key.
141
+ * @param breakpointList the list of breakpoints
142
+ * @internal
143
+ */
144
+ export function generateViewportSizeTypeInfoRefs(breakpointList: ViewportSizeTypeInfo[]): Dictionary<ViewportSizeTypeInfo> {
145
+ return Object.freeze(
146
+ breakpointList.reduce<Dictionary<ViewportSizeTypeInfo>>((dictionary, breakpoint) => {
147
+ dictionary[breakpoint.name] = breakpoint;
148
+ dictionary[breakpoint.type] = breakpoint;
149
+ return dictionary;
150
+ }, {})
151
+ );
152
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,28 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "es2022",
4
+ "forceConsistentCasingInFileNames": true,
5
+ "strict": true,
6
+ "noImplicitOverride": true,
7
+ "noPropertyAccessFromIndexSignature": true,
8
+ "noImplicitReturns": true,
9
+ "noFallthroughCasesInSwitch": true
10
+ },
11
+ "files": [],
12
+ "include": [],
13
+ "references": [
14
+ {
15
+ "path": "./tsconfig.lib.json"
16
+ },
17
+ {
18
+ "path": "./tsconfig.spec.json"
19
+ }
20
+ ],
21
+ "extends": "../../tsconfig.base.json",
22
+ "angularCompilerOptions": {
23
+ "enableI18nLegacyMessageIdFormat": false,
24
+ "strictInjectionParameters": true,
25
+ "strictInputAccessModifiers": true,
26
+ "strictTemplates": true
27
+ }
28
+ }
@@ -0,0 +1,12 @@
1
+ {
2
+ "extends": "./tsconfig.json",
3
+ "compilerOptions": {
4
+ "outDir": "../../dist/out-tsc",
5
+ "declaration": true,
6
+ "declarationMap": true,
7
+ "inlineSources": true,
8
+ "types": []
9
+ },
10
+ "exclude": ["src/**/*.spec.ts", "src/test-setup.ts", "jest.config.ts", "src/**/*.test.ts"],
11
+ "include": ["src/**/*.ts"]
12
+ }
@@ -0,0 +1,9 @@
1
+ {
2
+ "extends": "./tsconfig.lib.json",
3
+ "compilerOptions": {
4
+ "declarationMap": false
5
+ },
6
+ "angularCompilerOptions": {
7
+ "compilationMode": "partial"
8
+ }
9
+ }
@@ -0,0 +1,11 @@
1
+ {
2
+ "extends": "./tsconfig.json",
3
+ "compilerOptions": {
4
+ "outDir": "../../dist/out-tsc",
5
+ "module": "commonjs",
6
+ "target": "es2016",
7
+ "types": ["jest", "node"]
8
+ },
9
+ "files": ["src/test-setup.ts"],
10
+ "include": ["jest.config.ts", "src/**/*.test.ts", "src/**/*.spec.ts", "src/**/*.d.ts"]
11
+ }