@versatiles/style 5.2.6 → 5.2.7

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 (130) hide show
  1. package/dist/index.d.ts +274 -14
  2. package/dist/index.js +3650 -11
  3. package/dist/index.js.map +1 -1
  4. package/package.json +8 -8
  5. package/src/color/abstract.ts +83 -0
  6. package/src/color/hsl.test.ts +182 -0
  7. package/src/color/hsl.ts +122 -0
  8. package/src/color/hsv.test.ts +174 -0
  9. package/src/color/hsv.ts +100 -0
  10. package/src/color/index.test.ts +119 -0
  11. package/src/color/index.ts +38 -0
  12. package/src/color/random.test.ts +35 -0
  13. package/src/color/random.ts +165 -0
  14. package/src/color/rgb.test.ts +227 -0
  15. package/src/color/rgb.ts +248 -0
  16. package/src/color/utils.test.ts +86 -0
  17. package/src/color/utils.ts +13 -0
  18. package/src/guess_style/guess_style.test.ts +134 -0
  19. package/src/guess_style/guess_style.ts +166 -0
  20. package/{dist/guess_style/index.d.ts → src/guess_style/index.ts} +1 -0
  21. package/src/index.test.ts +77 -0
  22. package/src/index.ts +18 -0
  23. package/src/lib/utils.test.ts +197 -0
  24. package/src/lib/utils.ts +134 -0
  25. package/{dist/shortbread/index.d.ts → src/shortbread/index.ts} +1 -0
  26. package/src/shortbread/layers.test.ts +36 -0
  27. package/src/shortbread/layers.ts +564 -0
  28. package/src/shortbread/properties.test.ts +44 -0
  29. package/src/shortbread/properties.ts +142 -0
  30. package/src/shortbread/template.test.ts +43 -0
  31. package/src/shortbread/template.ts +343 -0
  32. package/src/style_builder/decorator.test.ts +67 -0
  33. package/src/style_builder/decorator.ts +135 -0
  34. package/src/style_builder/recolor.test.ts +306 -0
  35. package/src/style_builder/recolor.ts +110 -0
  36. package/src/style_builder/style_builder.test.ts +103 -0
  37. package/src/style_builder/style_builder.ts +134 -0
  38. package/src/style_builder/types.ts +141 -0
  39. package/src/styles/LICENSE.md +41 -0
  40. package/src/styles/colorful.ts +1041 -0
  41. package/src/styles/eclipse.ts +11 -0
  42. package/{dist/styles/empty.d.ts → src/styles/empty.ts} +7 -3
  43. package/src/styles/graybeard.ts +11 -0
  44. package/src/styles/index.ts +33 -0
  45. package/src/styles/neutrino.ts +429 -0
  46. package/{dist/types/index.d.ts → src/types/index.ts} +1 -0
  47. package/{dist/types/maplibre.d.ts → src/types/maplibre.ts} +3 -0
  48. package/src/types/tilejson.test.ts +94 -0
  49. package/src/types/tilejson.ts +125 -0
  50. package/src/types/vector_layer.test.ts +64 -0
  51. package/src/types/vector_layer.ts +69 -0
  52. package/dist/color/abstract.d.ts +0 -34
  53. package/dist/color/abstract.js +0 -53
  54. package/dist/color/abstract.js.map +0 -1
  55. package/dist/color/hsl.d.ts +0 -23
  56. package/dist/color/hsl.js +0 -98
  57. package/dist/color/hsl.js.map +0 -1
  58. package/dist/color/hsv.d.ts +0 -20
  59. package/dist/color/hsv.js +0 -100
  60. package/dist/color/hsv.js.map +0 -1
  61. package/dist/color/index.d.ts +0 -6
  62. package/dist/color/index.js +0 -29
  63. package/dist/color/index.js.map +0 -1
  64. package/dist/color/random.d.ts +0 -9
  65. package/dist/color/random.js +0 -134
  66. package/dist/color/random.js.map +0 -1
  67. package/dist/color/rgb.d.ts +0 -28
  68. package/dist/color/rgb.js +0 -195
  69. package/dist/color/rgb.js.map +0 -1
  70. package/dist/color/utils.d.ts +0 -3
  71. package/dist/color/utils.js +0 -10
  72. package/dist/color/utils.js.map +0 -1
  73. package/dist/guess_style/guess_style.d.ts +0 -8
  74. package/dist/guess_style/guess_style.js +0 -147
  75. package/dist/guess_style/guess_style.js.map +0 -1
  76. package/dist/guess_style/index.js +0 -2
  77. package/dist/guess_style/index.js.map +0 -1
  78. package/dist/lib/utils.d.ts +0 -6
  79. package/dist/lib/utils.js +0 -126
  80. package/dist/lib/utils.js.map +0 -1
  81. package/dist/shortbread/index.js +0 -3
  82. package/dist/shortbread/index.js.map +0 -1
  83. package/dist/shortbread/layers.d.ts +0 -5
  84. package/dist/shortbread/layers.js +0 -521
  85. package/dist/shortbread/layers.js.map +0 -1
  86. package/dist/shortbread/properties.d.ts +0 -7
  87. package/dist/shortbread/properties.js +0 -125
  88. package/dist/shortbread/properties.js.map +0 -1
  89. package/dist/shortbread/template.d.ts +0 -4
  90. package/dist/shortbread/template.js +0 -339
  91. package/dist/shortbread/template.js.map +0 -1
  92. package/dist/style_builder/decorator.d.ts +0 -4
  93. package/dist/style_builder/decorator.js +0 -127
  94. package/dist/style_builder/decorator.js.map +0 -1
  95. package/dist/style_builder/recolor.d.ts +0 -22
  96. package/dist/style_builder/recolor.js +0 -89
  97. package/dist/style_builder/recolor.js.map +0 -1
  98. package/dist/style_builder/style_builder.d.ts +0 -15
  99. package/dist/style_builder/style_builder.js +0 -106
  100. package/dist/style_builder/style_builder.js.map +0 -1
  101. package/dist/style_builder/types.d.ts +0 -122
  102. package/dist/style_builder/types.js +0 -3
  103. package/dist/style_builder/types.js.map +0 -1
  104. package/dist/styles/colorful.d.ts +0 -11
  105. package/dist/styles/colorful.js +0 -956
  106. package/dist/styles/colorful.js.map +0 -1
  107. package/dist/styles/eclipse.d.ts +0 -5
  108. package/dist/styles/eclipse.js +0 -9
  109. package/dist/styles/eclipse.js.map +0 -1
  110. package/dist/styles/empty.js +0 -8
  111. package/dist/styles/empty.js.map +0 -1
  112. package/dist/styles/graybeard.d.ts +0 -5
  113. package/dist/styles/graybeard.js +0 -9
  114. package/dist/styles/graybeard.js.map +0 -1
  115. package/dist/styles/index.d.ts +0 -11
  116. package/dist/styles/index.js +0 -20
  117. package/dist/styles/index.js.map +0 -1
  118. package/dist/styles/neutrino.d.ts +0 -11
  119. package/dist/styles/neutrino.js +0 -401
  120. package/dist/styles/neutrino.js.map +0 -1
  121. package/dist/types/index.js +0 -3
  122. package/dist/types/index.js.map +0 -1
  123. package/dist/types/maplibre.js +0 -2
  124. package/dist/types/maplibre.js.map +0 -1
  125. package/dist/types/tilejson.d.ts +0 -32
  126. package/dist/types/tilejson.js +0 -87
  127. package/dist/types/tilejson.js.map +0 -1
  128. package/dist/types/vector_layer.d.ts +0 -14
  129. package/dist/types/vector_layer.js +0 -51
  130. package/dist/types/vector_layer.js.map +0 -1
@@ -0,0 +1,197 @@
1
+ import { Color } from '../color/index.js';
2
+ import { deepClone, isSimpleObject, isBasicType, deepMerge, resolveUrl, basename } from './utils.js';
3
+
4
+ describe('deepClone', () => {
5
+ it('clones primitive types correctly', () => {
6
+ expect(deepClone(10)).toBe(10);
7
+ expect(deepClone(true)).toBe(true);
8
+ expect(deepClone('string')).toBe('string');
9
+ });
10
+
11
+ it('clones an array correctly', () => {
12
+ const array = [1, 2, { a: 'b' }];
13
+ const clonedArray = deepClone(array);
14
+ expect(clonedArray).toEqual(array);
15
+ expect(clonedArray).not.toBe(array);
16
+ expect(clonedArray[2]).not.toBe(array[2]);
17
+ });
18
+
19
+ it('clones a simple object correctly', () => {
20
+ const obj = { a: 'b', c: 1, d: true };
21
+ const clonedObj = deepClone(obj);
22
+ expect(clonedObj).toEqual(obj);
23
+ expect(clonedObj).not.toBe(obj);
24
+ });
25
+
26
+ it('clones a Color instance correctly', () => {
27
+ const color = Color.parse('#FF5733');
28
+ const clonedColor = deepClone(color);
29
+ expect(clonedColor.asHex()).toBe(color.asHex());
30
+ expect(clonedColor).not.toBe(color);
31
+ });
32
+
33
+ it('throws an error for non-implemented types', () => {
34
+ const func = (): boolean => true;
35
+ expect(() => deepClone(func)).toThrow('Not implemented yet: "function" case');
36
+ });
37
+
38
+ it('throws an error for unexpected cases', () => {
39
+ const obj = Object.create(null); // No prototype
40
+ expect(() => deepClone(obj)).toThrow();
41
+ });
42
+ });
43
+
44
+ describe('isSimpleObject', () => {
45
+ it('identifies simple objects correctly', () => {
46
+ expect(isSimpleObject({ a: 1 })).toBe(true);
47
+ expect(isSimpleObject({})).toBe(true);
48
+ });
49
+
50
+ it('rejects non-objects', () => {
51
+ expect(isSimpleObject(1)).toBe(false);
52
+ expect(isSimpleObject('a')).toBe(false);
53
+ expect(isSimpleObject(true)).toBe(false);
54
+ });
55
+
56
+ it('rejects arrays', () => {
57
+ expect(isSimpleObject([1, 2, 3])).toBe(false);
58
+ });
59
+
60
+ it('rejects objects with prototype properties', () => {
61
+ class MyClass {
62
+ readonly property = true;
63
+ }
64
+ expect(isSimpleObject(new MyClass())).toBe(false);
65
+ });
66
+
67
+ it('rejects null objects', () => {
68
+ expect(isSimpleObject(null)).toBe(false);
69
+ });
70
+ });
71
+
72
+ describe('isBasicType', () => {
73
+ it('returns true for basic types', () => {
74
+ expect(isBasicType(1)).toBe(true);
75
+ expect(isBasicType('string')).toBe(true);
76
+ expect(isBasicType(true)).toBe(true);
77
+ expect(isBasicType(undefined)).toBe(true);
78
+ });
79
+
80
+ it('returns false for objects', () => {
81
+ expect(isBasicType({})).toBe(false);
82
+ expect(isBasicType([])).toBe(false);
83
+ expect(isBasicType(Color.parse('#FF5733'))).toBe(false);
84
+ });
85
+
86
+ it('throws an error for unsupported types like functions', () => {
87
+ expect(() => isBasicType(() => true)).toThrow('unknown type: function');
88
+ });
89
+ });
90
+
91
+ describe('deepMerge', () => {
92
+ it('merges simple objects correctly', () => {
93
+ const target = { a: 1, b: 2, c: 3 };
94
+ const source = { b: 3, c: 4 };
95
+ const result = deepMerge(target, source);
96
+ expect(result).toEqual({ a: 1, b: 3, c: 4 });
97
+ });
98
+
99
+ it('merges nested objects correctly', () => {
100
+ const target = { a: { d: 4, e: 3 }, b: 2 } as { a: { d?: number, e: number }, b: number };
101
+ const source = { a: { e: 5 }, b: 3 };
102
+ const result = deepMerge(target, source);
103
+ expect(result).toEqual({ a: { d: 4, e: 5 }, b: 3 });
104
+ });
105
+
106
+ it('overrides primitives with objects', () => {
107
+ const target = { a: 1, b: 2 } as { a: object | number, b: object | number };
108
+ const source = { a: { d: 4 }, b: { e: 5 } };
109
+ const result = deepMerge(target, source);
110
+ expect(result).toEqual({ a: { d: 4 }, b: { e: 5 } });
111
+ });
112
+
113
+ it('merges Color instances correctly', () => {
114
+ const target = { color: Color.parse('#FF5733') };
115
+ const source = { color: Color.parse('#33FF57') };
116
+ const result = deepMerge(target, source);
117
+ expect(result.color.asHex()).toBe('#33FF57');
118
+ });
119
+
120
+ it('throws error for unsupported cases', () => {
121
+ const target = { a: (): boolean => false } as { a: (() => boolean) | object };
122
+ const source = { a: { b: 1 } };
123
+ expect(() => deepMerge(target, source)).toThrow('Not implemented yet: "function" case');
124
+ });
125
+ });
126
+
127
+ describe('resolveUrl', () => {
128
+ it('resolves a relative URL with a base URL', () => {
129
+ expect(resolveUrl('http://example.com/', 'path/page')).toBe('http://example.com/path/page');
130
+ });
131
+
132
+ it('returns the same URL if the base URL is empty', () => {
133
+ expect(resolveUrl('', 'http://example.com/path/page')).toBe('http://example.com/path/page');
134
+ });
135
+
136
+ it('returns the correct URL if an absolute URL is used', () => {
137
+ expect(resolveUrl('http://example1.com', 'http://example.com/path/page')).toBe('http://example.com/path/page');
138
+ });
139
+
140
+ it('handles URLs with special characters', () => {
141
+ expect(resolveUrl('http://example.com/', 'path/{param}')).toBe('http://example.com/path/{param}');
142
+ });
143
+
144
+ it('handles URLs already containing encoded special characters', () => {
145
+ expect(resolveUrl('http://example.com/', 'path/%7Bparam%7D')).toBe('http://example.com/path/{param}');
146
+ });
147
+
148
+ it('throws an error for invalid base URLs', () => {
149
+ expect(() => resolveUrl('invalid-base', 'path/page')).toThrow('Invalid URL');
150
+ });
151
+ });
152
+
153
+ describe('basename', () => {
154
+ it('returns the last segment of a URL-like string', () => {
155
+ expect(basename('http://example.com/path/to/resource')).toBe('resource');
156
+ expect(basename('/path/to/file.txt')).toBe('file.txt');
157
+ expect(basename('folder/subfolder/item')).toBe('item');
158
+ });
159
+
160
+ it('removes trailing slashes before extracting basename', () => {
161
+ expect(basename('http://example.com/path/to/resource/')).toBe('resource');
162
+ expect(basename('/path/to/folder/')).toBe('folder');
163
+ expect(basename('directory/')).toBe('directory');
164
+ });
165
+
166
+ it('returns an empty string for root paths or URLs ending with a slash', () => {
167
+ expect(basename('http://example.com/')).toBe('');
168
+ expect(basename('/')).toBe('');
169
+ expect(basename('////')).toBe('');
170
+ });
171
+
172
+ it('returns the full string if no slashes are present', () => {
173
+ expect(basename('file')).toBe('file');
174
+ expect(basename('filename.txt')).toBe('filename.txt');
175
+ expect(basename('no/slash')).toBe('slash');
176
+ });
177
+
178
+ it('handles empty and null inputs gracefully', () => {
179
+ expect(basename('')).toBe('');
180
+ expect(basename(null as unknown as string)).toBe('');
181
+ expect(basename(undefined as unknown as string)).toBe('');
182
+ });
183
+
184
+ it('handles URLs with query strings or fragments', () => {
185
+ expect(basename('http://example.com/path/to/resource?query=param')).toBe('resource');
186
+ expect(basename('http://example.com/path/to/resource#section')).toBe('resource');
187
+ expect(basename('/path/to/resource?query=param')).toBe('resource');
188
+ expect(basename('/path/to/resource#section')).toBe('resource');
189
+ });
190
+
191
+ it('handles URLs with special characters', () => {
192
+ expect(basename('http://example.com/path/to/resource%20name')).toBe('resource%20name');
193
+ expect(basename('/path/to/resource-name')).toBe('resource-name');
194
+ expect(basename('http://example.com/resource_name/')).toBe('resource_name');
195
+ expect(basename('/path/to/resource+name')).toBe('resource+name');
196
+ });
197
+ });
@@ -0,0 +1,134 @@
1
+ import { Color } from '../color/index.js';
2
+
3
+ // Utility function to deep clone an object
4
+ export function deepClone<T>(obj: T): T {
5
+ const type = typeof obj;
6
+ if (type !== 'object') {
7
+ switch (type) {
8
+ case 'boolean':
9
+ case 'number':
10
+ case 'string':
11
+ case 'undefined':
12
+ return obj;
13
+ default: throw new Error(`Not implemented yet: "${type}" case`);
14
+ }
15
+ }
16
+
17
+ if (isSimpleObject(obj)) {
18
+ // @ts-expect-error: Too complicated to handle
19
+ return Object.fromEntries(Object.entries(obj).map(([key, value]) => [key, deepClone(value)]));
20
+ }
21
+
22
+ if (obj instanceof Array) {
23
+ // @ts-expect-error: Too complicated to handle
24
+ return obj.map((e: unknown) => deepClone(e));
25
+ }
26
+
27
+ if (obj instanceof Color) {
28
+ return obj.clone() as T;
29
+ }
30
+
31
+ if (obj == null) return obj;
32
+
33
+ console.log('obj', obj);
34
+ console.log('obj.prototype', Object.getPrototypeOf(obj));
35
+ throw Error();
36
+ }
37
+
38
+ export function isSimpleObject(item: unknown): item is object {
39
+ if (item === null) return false;
40
+ if (typeof item !== 'object') return false;
41
+ if (Array.isArray(item)) return false;
42
+ const prototypeKeyCount: number = Object.keys(Object.getPrototypeOf(item) as object).length;
43
+ if (prototypeKeyCount !== 0) return false;
44
+ if (item.constructor.name !== 'Object') return false;
45
+ return true;
46
+ }
47
+
48
+ export function isBasicType(item: unknown): item is boolean | number | string | undefined {
49
+ switch (typeof item) {
50
+ case 'boolean':
51
+ case 'number':
52
+ case 'string':
53
+ case 'undefined':
54
+ return true;
55
+ case 'object':
56
+ return false;
57
+ default:
58
+ throw Error('unknown type: ' + typeof item);
59
+ }
60
+ }
61
+
62
+ export function deepMerge<T extends object>(source0: T, ...sources: Partial<T>[]): T {
63
+ const target: T = deepClone(source0);
64
+
65
+ for (const source of sources) {
66
+ if (typeof source !== 'object') continue;
67
+ for (const key in source) {
68
+ if (!(key in source)) continue;
69
+
70
+ const sourceValue = source[key] as T[typeof key];
71
+
72
+ // *********
73
+ // overwrite
74
+ // *********
75
+ switch (typeof sourceValue) {
76
+ case 'number':
77
+ case 'string':
78
+ case 'boolean':
79
+ target[key] = sourceValue;
80
+ continue;
81
+ default:
82
+ }
83
+
84
+ if (isBasicType(target[key])) {
85
+ target[key] = deepClone(sourceValue);
86
+ continue;
87
+ }
88
+
89
+ if (sourceValue instanceof Color) {
90
+ // @ts-expect-error: Too complicated to handle
91
+ target[key] = sourceValue.clone();
92
+ continue;
93
+ }
94
+
95
+ if (isSimpleObject(target[key]) && isSimpleObject(sourceValue)) {
96
+ target[key] = deepMerge(target[key], sourceValue);
97
+ continue;
98
+ }
99
+
100
+ // *********
101
+ // merge
102
+ // *********
103
+
104
+ if (isSimpleObject(target[key]) && isSimpleObject(sourceValue)) {
105
+ target[key] = deepMerge(target[key], sourceValue);
106
+ continue;
107
+ }
108
+
109
+ console.log('target[key]:', target[key]);
110
+ console.log('source[key]:', source[key]);
111
+ throw Error('unpredicted case');
112
+ }
113
+ }
114
+ return target;
115
+ }
116
+
117
+ export function resolveUrl(base: string, url: string): string {
118
+ if (!base) return url;
119
+ url = new URL(url, base).href;
120
+ url = url.replace(/%7B/gi, '{');
121
+ url = url.replace(/%7D/gi, '}');
122
+ return url;
123
+ }
124
+
125
+ export function basename(url: string): string {
126
+ if (!url) return '';
127
+ try {
128
+ url = new URL(url, 'http://example.org').pathname;
129
+ } catch (_) {
130
+ // ignore
131
+ }
132
+ url = url.replace(/\/+$/, '');
133
+ return url.split('/').pop() ?? '';
134
+ }
@@ -1,2 +1,3 @@
1
+
1
2
  export { getShortbreadTemplate } from './template.js';
2
3
  export { getShortbreadLayers } from './layers.js';
@@ -0,0 +1,36 @@
1
+ import type { FillLayerSpecification, SymbolLayerSpecification } from '@maplibre/maplibre-gl-style-spec';
2
+ import { getShortbreadLayers } from './layers.js';
3
+ import { Language } from '../style_builder/types.js';
4
+
5
+ describe('layers', () => {
6
+ it('should return an array of MaplibreLayer', () => {
7
+ const language: Language = 'en';
8
+ const layers = getShortbreadLayers({ language });
9
+
10
+ expect(Array.isArray(layers)).toBe(true);
11
+ expect(layers).not.toHaveLength(0);
12
+ layers.forEach((layer) => {
13
+ expect(layer).toHaveProperty('id');
14
+ expect(layer).toHaveProperty('type');
15
+ });
16
+ });
17
+
18
+ it('should handle language suffix correctly', () => {
19
+ const language: Language = 'en';
20
+ const layers = getShortbreadLayers({ language });
21
+ const labelLayer = layers.find((layer) => layer.id === 'label-street-pedestrian') as SymbolLayerSpecification;
22
+
23
+ expect(labelLayer).toBeDefined();
24
+
25
+ expect(labelLayer.layout?.['text-field']).toContain('{name_en}');
26
+ });
27
+
28
+ it('should create appropriate filters for land layers', () => {
29
+ const language: Language = 'en';
30
+ const layers = getShortbreadLayers({ language });
31
+ const landLayer = layers.find((layer) => layer.id === 'land-agriculture') as FillLayerSpecification;
32
+
33
+ expect(landLayer).toBeDefined();
34
+ expect(landLayer.filter).toEqual(['all', ['in', 'kind', 'brownfield', 'farmland', 'farmyard', 'greenfield', 'greenhouse_horticulture', 'orchard', 'plant_nursery', 'vineyard']]);
35
+ });
36
+ });