@oscarpalmer/atoms 0.6.0 → 0.7.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.
package/dist/css/flex.css CHANGED
@@ -45,104 +45,104 @@
45
45
  gap: 0;
46
46
  }
47
47
 
48
- .ac-c {
48
+ .flex-ac-c {
49
49
  align-content: center;
50
50
  }
51
- .ac-fe {
51
+ .flex-ac-fe {
52
52
  align-content: flex-end;
53
53
  }
54
- .ac-fs {
54
+ .flex-ac-fs {
55
55
  align-content: flex-start;
56
56
  }
57
- .ac-s {
57
+ .flex-ac-s {
58
58
  align-content: stretch;
59
59
  }
60
60
 
61
- .ai-c {
61
+ .flex-ai-c {
62
62
  align-items: center;
63
63
  }
64
- .ai-fe {
64
+ .flex-ai-fe {
65
65
  align-items: flex-end;
66
66
  }
67
- .ai-fs {
67
+ .flex-ai-fs {
68
68
  align-items: flex-start;
69
69
  }
70
- .ai-s {
70
+ .flex-ai-s {
71
71
  align-items: stretch;
72
72
  }
73
73
 
74
- .as-c {
74
+ .flex-as-c {
75
75
  align-self: center;
76
76
  }
77
- .as-fe {
77
+ .flex-as-fe {
78
78
  align-self: flex-end;
79
79
  }
80
- .as-fs {
80
+ .flex-as-fs {
81
81
  align-self: flex-start;
82
82
  }
83
- .as-s {
83
+ .flex-as-s {
84
84
  align-self: stretch;
85
85
  }
86
86
 
87
- .ac-sa {
87
+ .flex-ac-sa {
88
88
  align-content: space-around;
89
89
  }
90
90
 
91
- .ac-sb {
91
+ .flex-ac-sb {
92
92
  align-content: space-between;
93
93
  }
94
94
 
95
- .ac-se {
95
+ .flex-ac-se {
96
96
  align-content: space-evenly;
97
97
  }
98
98
 
99
- .jc-c {
99
+ .flex-jc-c {
100
100
  justify-content: center;
101
101
  }
102
- .jc-fe {
102
+ .flex-jc-fe {
103
103
  justify-content: flex-end;
104
104
  }
105
- .jc-fs {
105
+ .flex-jc-fs {
106
106
  justify-content: flex-start;
107
107
  }
108
- .jc-s {
108
+ .flex-jc-s {
109
109
  justify-content: stretch;
110
110
  }
111
111
 
112
- .ji-c {
112
+ .flex-ji-c {
113
113
  justify-items: center;
114
114
  }
115
- .ji-fe {
115
+ .flex-ji-fe {
116
116
  justify-items: flex-end;
117
117
  }
118
- .ji-fs {
118
+ .flex-ji-fs {
119
119
  justify-items: flex-start;
120
120
  }
121
- .ji-s {
121
+ .flex-ji-s {
122
122
  justify-items: stretch;
123
123
  }
124
124
 
125
- .js-c {
125
+ .flex-js-c {
126
126
  justify-self: center;
127
127
  }
128
- .js-fe {
128
+ .flex-js-fe {
129
129
  justify-self: flex-end;
130
130
  }
131
- .js-fs {
131
+ .flex-js-fs {
132
132
  justify-self: flex-start;
133
133
  }
134
- .js-s {
134
+ .flex-js-s {
135
135
  justify-self: stretch;
136
136
  }
137
137
 
138
- .jc-sa {
138
+ .flex-jc-sa {
139
139
  justify-content: space-around;
140
140
  }
141
141
 
142
- .jc-sb {
142
+ .flex-jc-sb {
143
143
  justify-content: space-between;
144
144
  }
145
145
 
146
- .jc-se {
146
+ .flex-jc-se {
147
147
  justify-content: space-evenly;
148
148
  }
package/dist/js/atoms.js CHANGED
@@ -1,12 +1,105 @@
1
+ // src/js/element.ts
2
+ function findParentElement(origin, selector) {
3
+ if (origin == null || selector == null) {
4
+ return;
5
+ }
6
+ function matches(element) {
7
+ return typeof selector === "string" ? element.matches?.(selector) ?? false : typeof selector === "function" ? selector(element) : false;
8
+ }
9
+ if (matches(origin)) {
10
+ return origin;
11
+ }
12
+ let parent = origin.parentElement;
13
+ while (parent != null && !matches(parent)) {
14
+ if (parent === document.body) {
15
+ return;
16
+ }
17
+ parent = parent.parentElement;
18
+ }
19
+ return parent ?? undefined;
20
+ }
21
+ function getElementUnderPointer(all) {
22
+ const elements = Array.from(document.querySelectorAll(":hover")).filter((element) => {
23
+ const style = window.getComputedStyle(element);
24
+ return element.tagName !== "HEAD" && (typeof all === "boolean" && all ? true : style.pointerEvents !== "none" && style.visibility !== "hidden");
25
+ });
26
+ return elements[elements.length - 1];
27
+ }
28
+ // src/js/event.ts
29
+ function getPosition(event) {
30
+ let x;
31
+ let y;
32
+ if (event instanceof MouseEvent) {
33
+ x = event.clientX;
34
+ y = event.clientY;
35
+ } else if (event instanceof TouchEvent) {
36
+ x = event.touches[0]?.clientX;
37
+ y = event.touches[0]?.clientY;
38
+ }
39
+ return typeof x === "number" && typeof y === "number" ? { x, y } : undefined;
40
+ }
41
+ var supportsTouch = (() => {
42
+ let value = false;
43
+ try {
44
+ if ("matchMedia" in window) {
45
+ const media = matchMedia("(pointer: coarse)");
46
+ if (typeof media?.matches === "boolean") {
47
+ value = media.matches;
48
+ }
49
+ }
50
+ if (!value) {
51
+ value = "ontouchstart" in window || navigator.maxTouchPoints > 0 || (navigator.msMaxTouchPoints ?? 0) > 0;
52
+ }
53
+ } catch {
54
+ value = false;
55
+ }
56
+ return value;
57
+ })();
58
+ // src/js/number.ts
59
+ function clampNumber(value, min, max) {
60
+ return Math.min(Math.max(getNumber(value), getNumber(min)), getNumber(max));
61
+ }
62
+ function getNumber(value) {
63
+ if (typeof value === "number") {
64
+ return value;
65
+ }
66
+ if (typeof value === "symbol") {
67
+ return NaN;
68
+ }
69
+ let parsed = value?.valueOf?.() ?? value;
70
+ if (typeof parsed === "object") {
71
+ parsed = parsed?.toString() ?? parsed;
72
+ }
73
+ if (typeof parsed !== "string") {
74
+ return parsed == null ? NaN : typeof parsed === "number" ? parsed : +parsed;
75
+ }
76
+ if (zeroPattern.test(parsed)) {
77
+ return 0;
78
+ }
79
+ const trimmed = parsed.trim();
80
+ if (trimmed.length === 0) {
81
+ return NaN;
82
+ }
83
+ const isBinary = binaryPattern.test(trimmed);
84
+ if (isBinary || octalPattern.test(trimmed)) {
85
+ return parseInt(trimmed.slice(2), isBinary ? 2 : 8);
86
+ }
87
+ return +(hexadecimalPattern.test(trimmed) ? trimmed : trimmed.replace(separatorPattern, ""));
88
+ }
89
+ var binaryPattern = /^0b[01]+$/i;
90
+ var hexadecimalPattern = /^0x[0-9a-f]+$/i;
91
+ var octalPattern = /^0o[0-7]+$/i;
92
+ var separatorPattern = /_/g;
93
+ var zeroPattern = /^\s*0+\s*$/;
1
94
  // src/js/string.ts
2
95
  function createUuid() {
3
96
  return uuidTemplate.replace(/[018]/g, (substring) => (substring ^ crypto.getRandomValues(new Uint8Array(1))[0] & 15 >> substring / 4).toString(16));
4
97
  }
5
98
  function getString(value) {
6
- return typeof value === "string" ? value : String(value);
99
+ return typeof value === "string" ? value : typeof value?.toString === "function" ? value.toString() : String(value);
7
100
  }
8
101
  function isNullableOrWhitespace(value) {
9
- return value === undefined || value === null || getString(value).trim().length === 0;
102
+ return value == null || getString(value).trim().length === 0;
10
103
  }
11
104
  var uuidTemplate = "10000000-1000-4000-8000-100000000000";
12
105
  // src/js/value.ts
@@ -17,9 +110,6 @@ var _getValue = function(data, key) {
17
110
  if (data instanceof Map) {
18
111
  return data.get(key);
19
112
  }
20
- if (data instanceof Set) {
21
- return Array.from(data)[key];
22
- }
23
113
  return data[key];
24
114
  };
25
115
  var _setValue = function(data, key, value) {
@@ -28,27 +118,10 @@ var _setValue = function(data, key, value) {
28
118
  }
29
119
  if (data instanceof Map) {
30
120
  data.set(key, value);
31
- } else if (data instanceof Set) {
32
- _setValueInSet(data, key, value);
33
121
  } else {
34
122
  data[key] = value;
35
123
  }
36
124
  };
37
- var _setValueInSet = function(data, key, value) {
38
- const index = numberExpression.test(key) ? Number.parseInt(key, 10) : -1;
39
- if (index === -1 || index >= data.size) {
40
- data.add(value);
41
- return;
42
- }
43
- const array = Array.from(data);
44
- array.splice(index, 1, value);
45
- data.clear();
46
- const { length } = array;
47
- let position = Number(length);
48
- while (position--) {
49
- data.add(array[length - position - 1]);
50
- }
51
- };
52
125
  function getValue(data, key) {
53
126
  if (typeof data !== "object" || data === null || isNullableOrWhitespace(key)) {
54
127
  return;
@@ -58,7 +131,7 @@ function getValue(data, key) {
58
131
  let value = data;
59
132
  while (position--) {
60
133
  value = _getValue(value, parts[position]);
61
- if (value === undefined) {
134
+ if (value == null) {
62
135
  break;
63
136
  }
64
137
  }
@@ -68,7 +141,7 @@ function isArrayOrObject(value) {
68
141
  return constructors.has(value?.constructor?.name);
69
142
  }
70
143
  function isNullable(value) {
71
- return value === undefined || value === null;
144
+ return value == null;
72
145
  }
73
146
  function isObject(value) {
74
147
  return value?.constructor?.name === objectConstructor;
@@ -100,6 +173,7 @@ var objectConstructor = "Object";
100
173
  var constructors = new Set(["Array", objectConstructor]);
101
174
  var numberExpression = /^\d+$/;
102
175
  export {
176
+ supportsTouch,
103
177
  setValue,
104
178
  isObject,
105
179
  isNullableOrWhitespace,
@@ -107,5 +181,10 @@ export {
107
181
  isArrayOrObject,
108
182
  getValue,
109
183
  getString,
110
- createUuid
184
+ getPosition,
185
+ getNumber,
186
+ getElementUnderPointer,
187
+ findParentElement,
188
+ createUuid,
189
+ clampNumber
111
190
  };
package/package.json CHANGED
@@ -5,9 +5,10 @@
5
5
  },
6
6
  "description": "Sweet little atomic goodies…",
7
7
  "devDependencies": {
8
- "@biomejs/biome": "^1.4",
8
+ "@biomejs/biome": "^1.5",
9
+ "@happy-dom/global-registrator": "^13.3",
9
10
  "bun": "^1.0",
10
- "sass": "^1.69",
11
+ "sass": "^1.70",
11
12
  "typescript": "^5.3"
12
13
  },
13
14
  "exports": {
@@ -17,11 +18,7 @@
17
18
  },
18
19
  "./package.json": "./package.json"
19
20
  },
20
- "files": [
21
- "dist",
22
- "src",
23
- "types"
24
- ],
21
+ "files": ["dist", "src", "types"],
25
22
  "keywords": [],
26
23
  "license": "MIT",
27
24
  "main": "./dist/js/atoms.js",
@@ -35,12 +32,12 @@
35
32
  "build": "bun run build:css && bun run build:js && bun run types",
36
33
  "build:css": "bunx sass ./src/css:./dist/css --no-source-map",
37
34
  "build:js": "bunx bun build ./src/js/index.ts --outfile ./dist/js/atoms.js",
38
- "test": "bun test --coverage",
35
+ "test": "bun test --preload ./test/_preload.ts --coverage",
39
36
  "types": "bunx tsc -p ./tsconfig.json",
40
37
  "watch:css": "bunx sass ./src/css:./dist/css --no-source-map --watch",
41
38
  "watch:js": "bun build ./src/js/index.ts --outfile ./dist/js/atoms.js --watch"
42
39
  },
43
40
  "type": "module",
44
41
  "types": "./types/index.d.ts",
45
- "version": "0.6.0"
46
- }
42
+ "version": "0.7.0"
43
+ }
package/src/css/flex.scss CHANGED
@@ -61,7 +61,7 @@ $types: (a: 'align', j: 'justify');
61
61
 
62
62
  @each $typeKey, $typeValue in $types {
63
63
  @each $alignmentOriginKey, $alignmentOriginValue in $alignmentOrigins {
64
- .#{$typeKey}#{$alignmentOriginKey} {
64
+ .flex-#{$typeKey}#{$alignmentOriginKey} {
65
65
  @each $alignmentValueKey, $alignmentValueValue in $alignmentValues {
66
66
  &-#{$alignmentValueKey} {
67
67
  #{$typeValue}-#{$alignmentOriginValue}: #{$alignmentValueValue}
@@ -71,7 +71,7 @@ $types: (a: 'align', j: 'justify');
71
71
  }
72
72
 
73
73
  @each $justificationValueKey, $justificationValueValue in $justificationValues {
74
- .#{$typeKey}c-#{$justificationValueKey} {
74
+ .flex-#{$typeKey}c-#{$justificationValueKey} {
75
75
  #{$typeValue}-content: #{$justificationValueValue}
76
76
  }
77
77
  }
@@ -0,0 +1,58 @@
1
+ /**
2
+ * - Find the parent element that matches the selector
3
+ * - Matches may be found by a query string or a callback
4
+ */
5
+ export function findParentElement(
6
+ origin: Element,
7
+ selector: string | ((element: Element) => boolean),
8
+ ): Element | undefined {
9
+ if (origin == null || selector == null) {
10
+ return undefined;
11
+ }
12
+
13
+ function matches(element: Element): boolean {
14
+ return typeof selector === 'string'
15
+ ? element.matches?.(selector) ?? false
16
+ : typeof selector === 'function'
17
+ ? selector(element)
18
+ : false;
19
+ }
20
+
21
+ if (matches(origin)) {
22
+ return origin;
23
+ }
24
+
25
+ let parent: Element | null = origin.parentElement;
26
+
27
+ while (parent != null && !matches(parent)) {
28
+ if (parent === document.body) {
29
+ return undefined;
30
+ }
31
+
32
+ parent = parent.parentElement;
33
+ }
34
+
35
+ return parent ?? undefined;
36
+ }
37
+
38
+ /**
39
+ * - Get the most specific element under the pointer
40
+ * - Ignores elements with `pointer-events: none` and `visibility: hidden`
41
+ * - If `all` is `true`, all elements under the pointer are returned
42
+ */
43
+ export function getElementUnderPointer(all?: boolean): Element | undefined {
44
+ const elements = Array.from(document.querySelectorAll(':hover')).filter(
45
+ element => {
46
+ const style = window.getComputedStyle(element);
47
+
48
+ return (
49
+ element.tagName !== 'HEAD' &&
50
+ (typeof all === 'boolean' && all
51
+ ? true
52
+ : style.pointerEvents !== 'none' && style.visibility !== 'hidden')
53
+ );
54
+ },
55
+ );
56
+
57
+ return elements[elements.length - 1];
58
+ }
@@ -0,0 +1,57 @@
1
+ type NavigatorWithMsMaxTouchPoints = Navigator & {
2
+ msMaxTouchPoints: number;
3
+ };
4
+
5
+ type Position = {
6
+ x: number;
7
+ y: number;
8
+ };
9
+
10
+ /**
11
+ * Does the browser support touch events?
12
+ */
13
+ export const supportsTouch = (() => {
14
+ let value = false;
15
+
16
+ try {
17
+ if ('matchMedia' in window) {
18
+ const media = matchMedia('(pointer: coarse)');
19
+
20
+ if (typeof media?.matches === 'boolean') {
21
+ value = media.matches;
22
+ }
23
+ }
24
+
25
+ if (!value) {
26
+ value =
27
+ 'ontouchstart' in window ||
28
+ navigator.maxTouchPoints > 0 ||
29
+ ((navigator as NavigatorWithMsMaxTouchPoints).msMaxTouchPoints ?? 0) >
30
+ 0;
31
+ }
32
+ } catch {
33
+ value = false;
34
+ }
35
+
36
+ return value;
37
+ })();
38
+
39
+ /**
40
+ * Get the X- and Y-coordinates from a pointer event
41
+ */
42
+ export function getPosition(
43
+ event: MouseEvent | TouchEvent,
44
+ ): Position | undefined {
45
+ let x: number | undefined;
46
+ let y: number | undefined;
47
+
48
+ if (event instanceof MouseEvent) {
49
+ x = event.clientX;
50
+ y = event.clientY;
51
+ } else if (event instanceof TouchEvent) {
52
+ x = event.touches[0]?.clientX;
53
+ y = event.touches[0]?.clientY;
54
+ }
55
+
56
+ return typeof x === 'number' && typeof y === 'number' ? {x, y} : undefined;
57
+ }
package/src/js/index.ts CHANGED
@@ -1,2 +1,5 @@
1
+ export * from './element';
2
+ export * from './event';
3
+ export * from './number';
1
4
  export * from './string';
2
5
  export * from './value';
@@ -0,0 +1,56 @@
1
+ const binaryPattern = /^0b[01]+$/i;
2
+ const hexadecimalPattern = /^0x[0-9a-f]+$/i;
3
+ const octalPattern = /^0o[0-7]+$/i;
4
+ const separatorPattern = /_/g;
5
+ const zeroPattern = /^\s*0+\s*$/;
6
+
7
+ /**
8
+ * Clamps a number between a minimum and maximum value
9
+ */
10
+ export function clampNumber(value: number, min: number, max: number): number {
11
+ return Math.min(Math.max(getNumber(value), getNumber(min)), getNumber(max));
12
+ }
13
+
14
+ /**
15
+ * - Gets the number value from an unknown value
16
+ * - Returns `NaN` if the value is `undefined`, `null`, or cannot be parsed
17
+ */
18
+ export function getNumber(value: unknown): number {
19
+ if (typeof value === 'number') {
20
+ return value;
21
+ }
22
+
23
+ if (typeof value === 'symbol') {
24
+ return NaN;
25
+ }
26
+
27
+ let parsed = value?.valueOf?.() ?? value;
28
+
29
+ if (typeof parsed === 'object') {
30
+ parsed = parsed?.toString() ?? parsed;
31
+ }
32
+
33
+ if (typeof parsed !== 'string') {
34
+ return parsed == null ? NaN : typeof parsed === 'number' ? parsed : +parsed;
35
+ }
36
+
37
+ if (zeroPattern.test(parsed)) {
38
+ return 0;
39
+ }
40
+
41
+ const trimmed = parsed.trim();
42
+
43
+ if (trimmed.length === 0) {
44
+ return NaN;
45
+ }
46
+
47
+ const isBinary = binaryPattern.test(trimmed);
48
+
49
+ if (isBinary || octalPattern.test(trimmed)) {
50
+ return parseInt(trimmed.slice(2), isBinary ? 2 : 8);
51
+ }
52
+
53
+ return +(hexadecimalPattern.test(trimmed)
54
+ ? trimmed
55
+ : trimmed.replace(separatorPattern, ''));
56
+ }
package/src/js/string.ts CHANGED
@@ -16,7 +16,11 @@ export function createUuid(): string {
16
16
  * Get the string value from any value
17
17
  */
18
18
  export function getString(value: unknown): string {
19
- return typeof value === 'string' ? value : String(value);
19
+ return typeof value === 'string'
20
+ ? value
21
+ : typeof value?.toString === 'function'
22
+ ? value.toString()
23
+ : String(value);
20
24
  }
21
25
 
22
26
  /**
@@ -25,9 +29,5 @@ export function getString(value: unknown): string {
25
29
  export function isNullableOrWhitespace(
26
30
  value: unknown,
27
31
  ): value is undefined | null | '' {
28
- return (
29
- value === undefined ||
30
- value === null ||
31
- getString(value).trim().length === 0
32
- );
32
+ return value == null || getString(value).trim().length === 0;
33
33
  }
package/src/js/value.ts CHANGED
@@ -22,10 +22,6 @@ function _getValue(data: ValueObject, key: string): unknown {
22
22
  return data.get(key as never);
23
23
  }
24
24
 
25
- if (data instanceof Set) {
26
- return Array.from(data)[key as never];
27
- }
28
-
29
25
  return data[key as never];
30
26
  }
31
27
 
@@ -39,42 +35,11 @@ function _setValue(data: ValueObject, key: string, value: unknown): void {
39
35
 
40
36
  if (data instanceof Map) {
41
37
  data.set(key as never, value);
42
- } else if (data instanceof Set) {
43
- _setValueInSet(data, key, value);
44
38
  } else {
45
39
  data[key as never] = value as never;
46
40
  }
47
41
  }
48
42
 
49
- /**
50
- * - Internal function to set a value in a `Set`
51
- * - If key is not a valid index or if it is greater than or equal to the length of the set, we simply append it to the set
52
- * - If the index is less than the size of the set, we convert the set to an array, splice the value into the array, and then convert the array back to a set
53
- */
54
- function _setValueInSet(data: Set<unknown>, key: string, value: unknown): void {
55
- const index = numberExpression.test(key) ? Number.parseInt(key, 10) : -1;
56
-
57
- if (index === -1 || index >= data.size) {
58
- data.add(value);
59
-
60
- return;
61
- }
62
-
63
- const array = Array.from(data);
64
-
65
- array.splice(index, 1, value);
66
-
67
- data.clear();
68
-
69
- const {length} = array;
70
-
71
- let position = Number(length);
72
-
73
- while (position--) {
74
- data.add(array[length - position - 1]);
75
- }
76
- }
77
-
78
43
  /**
79
44
  * - Get the value from an object using a key path
80
45
  * - You can retrieve a nested value by using dot notation, e.g., `foo.bar.baz`
@@ -97,7 +62,7 @@ export function getValue(data: ValueObject, key: Key): unknown {
97
62
  while (position--) {
98
63
  value = _getValue(value, parts[position]) as ValueObject;
99
64
 
100
- if (value === undefined) {
65
+ if (value == null) {
101
66
  break;
102
67
  }
103
68
  }
@@ -116,7 +81,7 @@ export function isArrayOrObject(value: unknown): value is ArrayOrObject {
116
81
  * Is the value undefined or null?
117
82
  */
118
83
  export function isNullable(value: unknown): value is undefined | null {
119
- return value === undefined || value === null;
84
+ return value == null;
120
85
  }
121
86
 
122
87
  /**
@@ -0,0 +1,11 @@
1
+ /**
2
+ * - Find the parent element that matches the selector
3
+ * - Matches may be found by a query string or a callback
4
+ */
5
+ export declare function findParentElement(origin: Element, selector: string | ((element: Element) => boolean)): Element | undefined;
6
+ /**
7
+ * - Get the most specific element under the pointer
8
+ * - Ignores elements with `pointer-events: none` and `visibility: hidden`
9
+ * - If `all` is `true`, all elements under the pointer are returned
10
+ */
11
+ export declare function getElementUnderPointer(all?: boolean): Element | undefined;
@@ -0,0 +1,13 @@
1
+ type Position = {
2
+ x: number;
3
+ y: number;
4
+ };
5
+ /**
6
+ * Does the browser support touch events?
7
+ */
8
+ export declare const supportsTouch: boolean;
9
+ /**
10
+ * Get the X- and Y-coordinates from a pointer event
11
+ */
12
+ export declare function getPosition(event: MouseEvent | TouchEvent): Position | undefined;
13
+ export {};
package/types/index.d.ts CHANGED
@@ -1,2 +1,5 @@
1
+ export * from './element';
2
+ export * from './event';
3
+ export * from './number';
1
4
  export * from './string';
2
5
  export * from './value';
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Clamps a number between a minimum and maximum value
3
+ */
4
+ export declare function clampNumber(value: number, min: number, max: number): number;
5
+ /**
6
+ * - Gets the number value from an unknown value
7
+ * - Returns `NaN` if the value is `undefined`, `null`, or cannot be parsed
8
+ */
9
+ export declare function getNumber(value: unknown): number;