@oscarpalmer/atoms 0.5.2 → 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 +30 -30
- package/dist/js/atoms.js +171 -17
- package/package.json +14 -38
- package/src/css/flex.scss +2 -2
- package/src/js/element.ts +58 -0
- package/src/js/event.ts +57 -0
- package/src/js/index.ts +3 -0
- package/src/js/number.ts +56 -0
- package/src/js/string.ts +6 -4
- package/src/js/value.ts +114 -9
- package/types/element.d.ts +11 -0
- package/types/event.d.ts +13 -0
- package/types/index.d.ts +3 -0
- package/types/number.d.ts +9 -0
- package/types/value.d.ts +23 -3
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,36 +1,190 @@
|
|
|
1
|
-
// src/js/
|
|
2
|
-
function
|
|
3
|
-
if (
|
|
1
|
+
// src/js/element.ts
|
|
2
|
+
function findParentElement(origin, selector) {
|
|
3
|
+
if (origin == null || selector == null) {
|
|
4
4
|
return;
|
|
5
5
|
}
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
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;
|
|
12
55
|
}
|
|
13
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));
|
|
14
61
|
}
|
|
15
|
-
function
|
|
16
|
-
|
|
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, ""));
|
|
17
88
|
}
|
|
18
|
-
|
|
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*$/;
|
|
19
94
|
// src/js/string.ts
|
|
20
95
|
function createUuid() {
|
|
21
96
|
return uuidTemplate.replace(/[018]/g, (substring) => (substring ^ crypto.getRandomValues(new Uint8Array(1))[0] & 15 >> substring / 4).toString(16));
|
|
22
97
|
}
|
|
23
|
-
function getString(
|
|
24
|
-
return typeof
|
|
98
|
+
function getString(value) {
|
|
99
|
+
return typeof value === "string" ? value : typeof value?.toString === "function" ? value.toString() : String(value);
|
|
25
100
|
}
|
|
26
|
-
function isNullableOrWhitespace(
|
|
27
|
-
return
|
|
101
|
+
function isNullableOrWhitespace(value) {
|
|
102
|
+
return value == null || getString(value).trim().length === 0;
|
|
28
103
|
}
|
|
29
104
|
var uuidTemplate = "10000000-1000-4000-8000-100000000000";
|
|
105
|
+
// src/js/value.ts
|
|
106
|
+
var _getValue = function(data, key) {
|
|
107
|
+
if (typeof data !== "object" || data === null || badProperties.has(key)) {
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
if (data instanceof Map) {
|
|
111
|
+
return data.get(key);
|
|
112
|
+
}
|
|
113
|
+
return data[key];
|
|
114
|
+
};
|
|
115
|
+
var _setValue = function(data, key, value) {
|
|
116
|
+
if (typeof data !== "object" || data === null || badProperties.has(key)) {
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
if (data instanceof Map) {
|
|
120
|
+
data.set(key, value);
|
|
121
|
+
} else {
|
|
122
|
+
data[key] = value;
|
|
123
|
+
}
|
|
124
|
+
};
|
|
125
|
+
function getValue(data, key) {
|
|
126
|
+
if (typeof data !== "object" || data === null || isNullableOrWhitespace(key)) {
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
const parts = getString(key).split(".").reverse();
|
|
130
|
+
let position = parts.length;
|
|
131
|
+
let value = data;
|
|
132
|
+
while (position--) {
|
|
133
|
+
value = _getValue(value, parts[position]);
|
|
134
|
+
if (value == null) {
|
|
135
|
+
break;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
return value;
|
|
139
|
+
}
|
|
140
|
+
function isArrayOrObject(value) {
|
|
141
|
+
return constructors.has(value?.constructor?.name);
|
|
142
|
+
}
|
|
143
|
+
function isNullable(value) {
|
|
144
|
+
return value == null;
|
|
145
|
+
}
|
|
146
|
+
function isObject(value) {
|
|
147
|
+
return value?.constructor?.name === objectConstructor;
|
|
148
|
+
}
|
|
149
|
+
function setValue(data, key, value) {
|
|
150
|
+
if (typeof data !== "object" || data === null || isNullableOrWhitespace(key)) {
|
|
151
|
+
return data;
|
|
152
|
+
}
|
|
153
|
+
const parts = getString(key).split(".").reverse();
|
|
154
|
+
let position = parts.length;
|
|
155
|
+
let target = data;
|
|
156
|
+
while (position--) {
|
|
157
|
+
const key2 = parts[position];
|
|
158
|
+
if (position === 0) {
|
|
159
|
+
_setValue(target, key2, value);
|
|
160
|
+
break;
|
|
161
|
+
}
|
|
162
|
+
let next = _getValue(target, key2);
|
|
163
|
+
if (typeof next !== "object" || next === null) {
|
|
164
|
+
next = numberExpression.test(parts[position - 1]) ? [] : {};
|
|
165
|
+
target[key2] = next;
|
|
166
|
+
}
|
|
167
|
+
target = next;
|
|
168
|
+
}
|
|
169
|
+
return data;
|
|
170
|
+
}
|
|
171
|
+
var badProperties = new Set(["__proto__", "constructor", "prototype"]);
|
|
172
|
+
var objectConstructor = "Object";
|
|
173
|
+
var constructors = new Set(["Array", objectConstructor]);
|
|
174
|
+
var numberExpression = /^\d+$/;
|
|
30
175
|
export {
|
|
176
|
+
supportsTouch,
|
|
177
|
+
setValue,
|
|
178
|
+
isObject,
|
|
31
179
|
isNullableOrWhitespace,
|
|
32
180
|
isNullable,
|
|
181
|
+
isArrayOrObject,
|
|
33
182
|
getValue,
|
|
34
183
|
getString,
|
|
35
|
-
|
|
184
|
+
getPosition,
|
|
185
|
+
getNumber,
|
|
186
|
+
getElementUnderPointer,
|
|
187
|
+
findParentElement,
|
|
188
|
+
createUuid,
|
|
189
|
+
clampNumber
|
|
36
190
|
};
|
package/package.json
CHANGED
|
@@ -5,13 +5,11 @@
|
|
|
5
5
|
},
|
|
6
6
|
"description": "Sweet little atomic goodies…",
|
|
7
7
|
"devDependencies": {
|
|
8
|
-
"@
|
|
8
|
+
"@biomejs/biome": "^1.5",
|
|
9
|
+
"@happy-dom/global-registrator": "^13.3",
|
|
9
10
|
"bun": "^1.0",
|
|
10
|
-
"
|
|
11
|
-
"
|
|
12
|
-
"sass": "^1.69",
|
|
13
|
-
"typescript": "^5.3",
|
|
14
|
-
"xo": "^0.56"
|
|
11
|
+
"sass": "^1.70",
|
|
12
|
+
"typescript": "^5.3"
|
|
15
13
|
},
|
|
16
14
|
"exports": {
|
|
17
15
|
".": {
|
|
@@ -20,48 +18,26 @@
|
|
|
20
18
|
},
|
|
21
19
|
"./package.json": "./package.json"
|
|
22
20
|
},
|
|
23
|
-
"files": [
|
|
24
|
-
"dist",
|
|
25
|
-
"src",
|
|
26
|
-
"types"
|
|
27
|
-
],
|
|
21
|
+
"files": ["dist", "src", "types"],
|
|
28
22
|
"keywords": [],
|
|
29
23
|
"license": "MIT",
|
|
30
24
|
"main": "./dist/js/atoms.js",
|
|
31
25
|
"module": "./dist/js/atoms.js",
|
|
32
26
|
"name": "@oscarpalmer/atoms",
|
|
33
|
-
"prettier": {
|
|
34
|
-
"arrowParens": "avoid",
|
|
35
|
-
"bracketSpacing": false,
|
|
36
|
-
"singleQuote": true,
|
|
37
|
-
"switchIndent": true,
|
|
38
|
-
"trailingComma": "all",
|
|
39
|
-
"useTabs": true
|
|
40
|
-
},
|
|
41
27
|
"repository": {
|
|
42
28
|
"type": "git",
|
|
43
29
|
"url": "git+https://github.com/oscarpalmer/atoms.git"
|
|
44
30
|
},
|
|
45
31
|
"scripts": {
|
|
46
|
-
"build": "
|
|
47
|
-
"build:css": "
|
|
48
|
-
"build:js": "
|
|
49
|
-
"test": "
|
|
50
|
-
"types": "
|
|
51
|
-
"watch:css": "
|
|
52
|
-
"watch:js": "
|
|
32
|
+
"build": "bun run build:css && bun run build:js && bun run types",
|
|
33
|
+
"build:css": "bunx sass ./src/css:./dist/css --no-source-map",
|
|
34
|
+
"build:js": "bunx bun build ./src/js/index.ts --outfile ./dist/js/atoms.js",
|
|
35
|
+
"test": "bun test --preload ./test/_preload.ts --coverage",
|
|
36
|
+
"types": "bunx tsc -p ./tsconfig.json",
|
|
37
|
+
"watch:css": "bunx sass ./src/css:./dist/css --no-source-map --watch",
|
|
38
|
+
"watch:js": "bun build ./src/js/index.ts --outfile ./dist/js/atoms.js --watch"
|
|
53
39
|
},
|
|
54
40
|
"type": "module",
|
|
55
41
|
"types": "./types/index.d.ts",
|
|
56
|
-
"version": "0.
|
|
57
|
-
|
|
58
|
-
"envs": [
|
|
59
|
-
"browser"
|
|
60
|
-
],
|
|
61
|
-
"prettier": true,
|
|
62
|
-
"rules": {
|
|
63
|
-
"import/extensions": "off",
|
|
64
|
-
"import/no-cycle": "off"
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
}
|
|
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
|
-
|
|
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
|
-
|
|
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
|
+
}
|
package/src/js/event.ts
ADDED
|
@@ -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
package/src/js/number.ts
ADDED
|
@@ -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
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
import {isNullable} from './value';
|
|
2
|
-
|
|
3
1
|
const uuidTemplate = '10000000-1000-4000-8000-100000000000';
|
|
4
2
|
|
|
5
3
|
/**
|
|
@@ -18,7 +16,11 @@ export function createUuid(): string {
|
|
|
18
16
|
* Get the string value from any value
|
|
19
17
|
*/
|
|
20
18
|
export function getString(value: unknown): string {
|
|
21
|
-
return typeof value === 'string'
|
|
19
|
+
return typeof value === 'string'
|
|
20
|
+
? value
|
|
21
|
+
: typeof value?.toString === 'function'
|
|
22
|
+
? value.toString()
|
|
23
|
+
: String(value);
|
|
22
24
|
}
|
|
23
25
|
|
|
24
26
|
/**
|
|
@@ -27,5 +29,5 @@ export function getString(value: unknown): string {
|
|
|
27
29
|
export function isNullableOrWhitespace(
|
|
28
30
|
value: unknown,
|
|
29
31
|
): value is undefined | null | '' {
|
|
30
|
-
return
|
|
32
|
+
return value == null || getString(value).trim().length === 0;
|
|
31
33
|
}
|
package/src/js/value.ts
CHANGED
|
@@ -1,10 +1,51 @@
|
|
|
1
1
|
import {getString, isNullableOrWhitespace} from './string';
|
|
2
2
|
|
|
3
|
+
export type ArrayOrObject = unknown[] | GenericObject;
|
|
4
|
+
export type GenericObject = Record<string, unknown>;
|
|
5
|
+
export type Key = number | string;
|
|
6
|
+
export type ValueObject = ArrayOrObject | Map<unknown, unknown> | Set<unknown>;
|
|
7
|
+
|
|
8
|
+
const badProperties = new Set(['__proto__', 'constructor', 'prototype']);
|
|
9
|
+
const objectConstructor = 'Object';
|
|
10
|
+
const constructors = new Set(['Array', objectConstructor]);
|
|
11
|
+
const numberExpression = /^\d+$/;
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Internal function to get a value from an object
|
|
15
|
+
*/
|
|
16
|
+
function _getValue(data: ValueObject, key: string): unknown {
|
|
17
|
+
if (typeof data !== 'object' || data === null || badProperties.has(key)) {
|
|
18
|
+
return undefined;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
if (data instanceof Map) {
|
|
22
|
+
return data.get(key as never);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
return data[key as never];
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Internal function to set a value in an object
|
|
30
|
+
*/
|
|
31
|
+
function _setValue(data: ValueObject, key: string, value: unknown): void {
|
|
32
|
+
if (typeof data !== 'object' || data === null || badProperties.has(key)) {
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
if (data instanceof Map) {
|
|
37
|
+
data.set(key as never, value);
|
|
38
|
+
} else {
|
|
39
|
+
data[key as never] = value as never;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
3
43
|
/**
|
|
4
|
-
* - Get the value from an object using a path
|
|
5
|
-
* - You can retrieve a nested value by using
|
|
44
|
+
* - Get the value from an object using a key path
|
|
45
|
+
* - You can retrieve a nested value by using dot notation, e.g., `foo.bar.baz`
|
|
46
|
+
* - Returns `undefined` if the value is not found
|
|
6
47
|
*/
|
|
7
|
-
export function getValue(data:
|
|
48
|
+
export function getValue(data: ValueObject, key: Key): unknown {
|
|
8
49
|
if (
|
|
9
50
|
typeof data !== 'object' ||
|
|
10
51
|
data === null ||
|
|
@@ -13,22 +54,86 @@ export function getValue(data: unknown, key: unknown): unknown {
|
|
|
13
54
|
return undefined;
|
|
14
55
|
}
|
|
15
56
|
|
|
16
|
-
const parts = getString(key).split('.');
|
|
17
|
-
const length = parts.length;
|
|
57
|
+
const parts = getString(key).split('.').reverse();
|
|
18
58
|
|
|
19
|
-
let
|
|
59
|
+
let position = parts.length;
|
|
20
60
|
let value = data;
|
|
21
61
|
|
|
22
|
-
while (
|
|
23
|
-
value = value
|
|
62
|
+
while (position--) {
|
|
63
|
+
value = _getValue(value, parts[position]) as ValueObject;
|
|
64
|
+
|
|
65
|
+
if (value == null) {
|
|
66
|
+
break;
|
|
67
|
+
}
|
|
24
68
|
}
|
|
25
69
|
|
|
26
70
|
return value;
|
|
27
71
|
}
|
|
28
72
|
|
|
73
|
+
/**
|
|
74
|
+
* Is the value an array or a generic object?
|
|
75
|
+
*/
|
|
76
|
+
export function isArrayOrObject(value: unknown): value is ArrayOrObject {
|
|
77
|
+
return constructors.has((value as ArrayOrObject)?.constructor?.name);
|
|
78
|
+
}
|
|
79
|
+
|
|
29
80
|
/**
|
|
30
81
|
* Is the value undefined or null?
|
|
31
82
|
*/
|
|
32
83
|
export function isNullable(value: unknown): value is undefined | null {
|
|
33
|
-
return value
|
|
84
|
+
return value == null;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Is the value a generic object?
|
|
89
|
+
*/
|
|
90
|
+
export function isObject(value: unknown): value is GenericObject {
|
|
91
|
+
return (value as GenericObject)?.constructor?.name === objectConstructor;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* - Set the value in an object using a key path
|
|
96
|
+
* - You can set a nested value by using dot notation, e.g., `foo.bar.baz`
|
|
97
|
+
* - If a part of the path does not exist, it will be created, either as an array or a generic object, depending on the key
|
|
98
|
+
* - Returns the original object
|
|
99
|
+
*/
|
|
100
|
+
export function setValue<Model extends ValueObject>(
|
|
101
|
+
data: Model,
|
|
102
|
+
key: Key,
|
|
103
|
+
value: unknown,
|
|
104
|
+
): Model {
|
|
105
|
+
if (
|
|
106
|
+
typeof data !== 'object' ||
|
|
107
|
+
data === null ||
|
|
108
|
+
isNullableOrWhitespace(key)
|
|
109
|
+
) {
|
|
110
|
+
return data;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const parts = getString(key).split('.').reverse();
|
|
114
|
+
|
|
115
|
+
let position = parts.length;
|
|
116
|
+
let target: ValueObject = data;
|
|
117
|
+
|
|
118
|
+
while (position--) {
|
|
119
|
+
const key = parts[position] as never;
|
|
120
|
+
|
|
121
|
+
if (position === 0) {
|
|
122
|
+
_setValue(target, key, value);
|
|
123
|
+
|
|
124
|
+
break;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
let next = _getValue(target, key);
|
|
128
|
+
|
|
129
|
+
if (typeof next !== 'object' || next === null) {
|
|
130
|
+
next = numberExpression.test(parts[position - 1]) ? [] : {};
|
|
131
|
+
|
|
132
|
+
target[key] = next as never;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
target = next as ValueObject;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
return data;
|
|
34
139
|
}
|
|
@@ -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;
|
package/types/event.d.ts
ADDED
|
@@ -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
|
@@ -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;
|
package/types/value.d.ts
CHANGED
|
@@ -1,9 +1,29 @@
|
|
|
1
|
+
export type ArrayOrObject = unknown[] | GenericObject;
|
|
2
|
+
export type GenericObject = Record<string, unknown>;
|
|
3
|
+
export type Key = number | string;
|
|
4
|
+
export type ValueObject = ArrayOrObject | Map<unknown, unknown> | Set<unknown>;
|
|
1
5
|
/**
|
|
2
|
-
* - Get the value from an object using a path
|
|
3
|
-
* - You can retrieve a nested value by using
|
|
6
|
+
* - Get the value from an object using a key path
|
|
7
|
+
* - You can retrieve a nested value by using dot notation, e.g., `foo.bar.baz`
|
|
8
|
+
* - Returns `undefined` if the value is not found
|
|
4
9
|
*/
|
|
5
|
-
export declare function getValue(data:
|
|
10
|
+
export declare function getValue(data: ValueObject, key: Key): unknown;
|
|
11
|
+
/**
|
|
12
|
+
* Is the value an array or a generic object?
|
|
13
|
+
*/
|
|
14
|
+
export declare function isArrayOrObject(value: unknown): value is ArrayOrObject;
|
|
6
15
|
/**
|
|
7
16
|
* Is the value undefined or null?
|
|
8
17
|
*/
|
|
9
18
|
export declare function isNullable(value: unknown): value is undefined | null;
|
|
19
|
+
/**
|
|
20
|
+
* Is the value a generic object?
|
|
21
|
+
*/
|
|
22
|
+
export declare function isObject(value: unknown): value is GenericObject;
|
|
23
|
+
/**
|
|
24
|
+
* - Set the value in an object using a key path
|
|
25
|
+
* - You can set a nested value by using dot notation, e.g., `foo.bar.baz`
|
|
26
|
+
* - If a part of the path does not exist, it will be created, either as an array or a generic object, depending on the key
|
|
27
|
+
* - Returns the original object
|
|
28
|
+
*/
|
|
29
|
+
export declare function setValue<Model extends ValueObject>(data: Model, key: Key, value: unknown): Model;
|