@oscarpalmer/atoms 0.9.0 → 0.10.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.
@@ -0,0 +1,60 @@
1
+ // src/js/element/index.ts
2
+ function findElements(selector, context) {
3
+ const contexts = context === undefined ? [document] : findElements(context);
4
+ const elements = [];
5
+ if (typeof selector === "string") {
6
+ for (const context2 of contexts) {
7
+ elements.push(...Array.from(context2.querySelectorAll(selector) ?? []));
8
+ }
9
+ return elements;
10
+ }
11
+ const nodes = Array.isArray(selector) || selector instanceof NodeList ? selector : [selector];
12
+ for (const node of nodes) {
13
+ if (node instanceof Element && contexts.some((context2) => context2.contains(node))) {
14
+ elements.push(node);
15
+ }
16
+ }
17
+ return elements;
18
+ }
19
+ function findParentElement(origin, selector) {
20
+ if (origin == null || selector == null) {
21
+ return;
22
+ }
23
+ function matches(element) {
24
+ return typeof selector === "string" ? element.matches?.(selector) ?? false : typeof selector === "function" ? selector(element) : false;
25
+ }
26
+ if (matches(origin)) {
27
+ return origin;
28
+ }
29
+ let parent = origin.parentElement;
30
+ while (parent != null && !matches(parent)) {
31
+ if (parent === document.body) {
32
+ return;
33
+ }
34
+ parent = parent.parentElement;
35
+ }
36
+ return parent ?? undefined;
37
+ }
38
+ function getElementUnderPointer(skipIgnore) {
39
+ const elements = Array.from(document.querySelectorAll(":hover")).filter((element) => {
40
+ if (/^head$/i.test(element.tagName)) {
41
+ return false;
42
+ }
43
+ const style = getComputedStyle(element);
44
+ return typeof skipIgnore === "boolean" && skipIgnore || style.pointerEvents !== "none" && style.visibility !== "hidden";
45
+ });
46
+ return elements[elements.length - 1];
47
+ }
48
+ function getTextDirection(element) {
49
+ const direction = element.getAttribute("dir");
50
+ if (direction !== null && /^(ltr|rtl)$/i.test(direction)) {
51
+ return direction.toLowerCase();
52
+ }
53
+ return getComputedStyle?.(element)?.direction === "rtl" ? "rtl" : "ltr";
54
+ }
55
+ export {
56
+ getTextDirection,
57
+ getElementUnderPointer,
58
+ findParentElement,
59
+ findElements
60
+ };
@@ -0,0 +1,16 @@
1
+ // src/js/event.ts
2
+ function getEventPosition(event) {
3
+ let x;
4
+ let y;
5
+ if (event instanceof MouseEvent) {
6
+ x = event.clientX;
7
+ y = event.clientY;
8
+ } else if (event instanceof TouchEvent) {
9
+ x = event.touches[0]?.clientX;
10
+ y = event.touches[0]?.clientY;
11
+ }
12
+ return typeof x === "number" && typeof y === "number" ? { x, y } : undefined;
13
+ }
14
+ export {
15
+ getEventPosition
16
+ };
@@ -1,84 +1,3 @@
1
- // src/js/element/index.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(skipIgnore) {
22
- const elements = Array.from(document.querySelectorAll(":hover")).filter((element) => {
23
- if (/^head$/i.test(element.tagName)) {
24
- return false;
25
- }
26
- const style = getComputedStyle(element);
27
- return typeof skipIgnore === "boolean" && skipIgnore || style.pointerEvents !== "none" && style.visibility !== "hidden";
28
- });
29
- return elements[elements.length - 1];
30
- }
31
- function getTextDirection(element) {
32
- const direction = element.getAttribute("dir");
33
- if (direction !== null && /^(ltr|rtl)$/i.test(direction)) {
34
- return direction.toLowerCase();
35
- }
36
- return getComputedStyle?.(element)?.direction === "rtl" ? "rtl" : "ltr";
37
- }
38
- // src/js/event.ts
39
- function getPosition(event) {
40
- let x;
41
- let y;
42
- if (event instanceof MouseEvent) {
43
- x = event.clientX;
44
- y = event.clientY;
45
- } else if (event instanceof TouchEvent) {
46
- x = event.touches[0]?.clientX;
47
- y = event.touches[0]?.clientY;
48
- }
49
- return typeof x === "number" && typeof y === "number" ? { x, y } : undefined;
50
- }
51
- // src/js/number.ts
52
- function clampNumber(value, min, max) {
53
- return Math.min(Math.max(getNumber(value), getNumber(min)), getNumber(max));
54
- }
55
- function getNumber(value) {
56
- if (typeof value === "number") {
57
- return value;
58
- }
59
- if (typeof value === "symbol") {
60
- return NaN;
61
- }
62
- let parsed = value?.valueOf?.() ?? value;
63
- if (typeof parsed === "object") {
64
- parsed = parsed?.toString() ?? parsed;
65
- }
66
- if (typeof parsed !== "string") {
67
- return parsed == null ? NaN : typeof parsed === "number" ? parsed : +parsed;
68
- }
69
- if (/^\s*0+\s*$/.test(parsed)) {
70
- return 0;
71
- }
72
- const trimmed = parsed.trim();
73
- if (trimmed.length === 0) {
74
- return NaN;
75
- }
76
- const isBinary = /^0b[01]+$/i.test(trimmed);
77
- if (isBinary || /^0o[0-7]+$/i.test(trimmed)) {
78
- return parseInt(trimmed.slice(2), isBinary ? 2 : 8);
79
- }
80
- return +(/^0x[0-9a-f]+$/i.test(trimmed) ? trimmed : trimmed.replace(/_/g, ""));
81
- }
82
1
  // src/js/string.ts
83
2
  function createUuid() {
84
3
  return "10000000-1000-4000-8000-100000000000".replace(/[018]/g, (substring) => (substring ^ crypto.getRandomValues(new Uint8Array(1))[0] & 15 >> substring / 4).toString(16));
@@ -89,6 +8,7 @@ function getString(value) {
89
8
  function isNullableOrWhitespace(value) {
90
9
  return value == null || getString(value).trim().length === 0;
91
10
  }
11
+
92
12
  // src/js/value.ts
93
13
  var _getValue = function(data, key) {
94
14
  if (typeof data !== "object" || data === null || /^(__proto__|constructor|prototype)$/i.test(key)) {
@@ -155,8 +75,204 @@ function setValue(data, key, value) {
155
75
  }
156
76
  return data;
157
77
  }
78
+
79
+ // src/js/number.ts
80
+ function clampNumber(value, min, max) {
81
+ return Math.min(Math.max(getNumber(value), getNumber(min)), getNumber(max));
82
+ }
83
+ function getNumber(value) {
84
+ if (typeof value === "number") {
85
+ return value;
86
+ }
87
+ if (typeof value === "symbol") {
88
+ return NaN;
89
+ }
90
+ let parsed = value?.valueOf?.() ?? value;
91
+ if (typeof parsed === "object") {
92
+ parsed = parsed?.toString() ?? parsed;
93
+ }
94
+ if (typeof parsed !== "string") {
95
+ return parsed == null ? NaN : typeof parsed === "number" ? parsed : +parsed;
96
+ }
97
+ if (/^\s*0+\s*$/.test(parsed)) {
98
+ return 0;
99
+ }
100
+ const trimmed = parsed.trim();
101
+ if (trimmed.length === 0) {
102
+ return NaN;
103
+ }
104
+ const isBinary = /^0b[01]+$/i.test(trimmed);
105
+ if (isBinary || /^0o[0-7]+$/i.test(trimmed)) {
106
+ return parseInt(trimmed.slice(2), isBinary ? 2 : 8);
107
+ }
108
+ return +(/^0x[0-9a-f]+$/i.test(trimmed) ? trimmed : trimmed.replace(/_/g, ""));
109
+ }
110
+
111
+ // src/js/timer.ts
112
+ function repeat(callback, options) {
113
+ const count = typeof options?.count === "number" ? options.count : Infinity;
114
+ return timer(callback, { ...{ count }, ...options ?? {} }).start();
115
+ }
116
+ var timer = function(callback, config) {
117
+ const options = {
118
+ afterCallback: typeof config.afterCallback === "function" ? config.afterCallback : undefined,
119
+ callback,
120
+ count: typeof config.count === "number" && config.count >= 1 ? config.count : 1,
121
+ interval: typeof config.interval === "number" && config.interval >= 0 ? config.interval : 0
122
+ };
123
+ const state = {
124
+ active: false
125
+ };
126
+ const timer2 = Object.create(null);
127
+ Object.defineProperties(timer2, {
128
+ active: {
129
+ get() {
130
+ return state.active;
131
+ }
132
+ },
133
+ restart: {
134
+ value() {
135
+ return work("restart", timer2, state, options);
136
+ }
137
+ },
138
+ start: {
139
+ value() {
140
+ return work("start", timer2, state, options);
141
+ }
142
+ },
143
+ stop: {
144
+ value() {
145
+ return work("stop", timer2, state, options);
146
+ }
147
+ }
148
+ });
149
+ return timer2;
150
+ };
151
+ function wait(callback, time) {
152
+ return timer(callback, {
153
+ count: 1,
154
+ interval: time
155
+ }).start();
156
+ }
157
+ var work = function(type, timer2, state, options) {
158
+ if (type === "start" && timer2.active || type === "stop" && !timer2.active) {
159
+ return timer2;
160
+ }
161
+ const { afterCallback, callback, count, interval } = options;
162
+ if (typeof state.frame === "number") {
163
+ cancelAnimationFrame(state.frame);
164
+ afterCallback?.(false);
165
+ }
166
+ if (type === "stop") {
167
+ state.active = false;
168
+ state.frame = undefined;
169
+ return timer2;
170
+ }
171
+ state.active = true;
172
+ const isRepeated = count > 0;
173
+ const milliseconds = 16.666666666666668;
174
+ let index = 0;
175
+ let start;
176
+ function step(timestamp) {
177
+ if (!state.active) {
178
+ return;
179
+ }
180
+ start ??= timestamp;
181
+ const elapsed = timestamp - start;
182
+ const maximum = elapsed + milliseconds;
183
+ const minimum = elapsed - milliseconds;
184
+ if (minimum < interval && interval < maximum) {
185
+ if (state.active) {
186
+ callback(isRepeated ? index : undefined);
187
+ }
188
+ index += 1;
189
+ if (index < count) {
190
+ start = undefined;
191
+ } else {
192
+ state.active = false;
193
+ state.frame = undefined;
194
+ afterCallback?.(true);
195
+ return;
196
+ }
197
+ }
198
+ state.frame = requestAnimationFrame(step);
199
+ }
200
+ state.frame = requestAnimationFrame(step);
201
+ return timer2;
202
+ };
203
+
204
+ // src/js/element/index.ts
205
+ function findElements(selector, context) {
206
+ const contexts = context === undefined ? [document] : findElements(context);
207
+ const elements = [];
208
+ if (typeof selector === "string") {
209
+ for (const context2 of contexts) {
210
+ elements.push(...Array.from(context2.querySelectorAll(selector) ?? []));
211
+ }
212
+ return elements;
213
+ }
214
+ const nodes = Array.isArray(selector) || selector instanceof NodeList ? selector : [selector];
215
+ for (const node of nodes) {
216
+ if (node instanceof Element && contexts.some((context2) => context2.contains(node))) {
217
+ elements.push(node);
218
+ }
219
+ }
220
+ return elements;
221
+ }
222
+ function findParentElement(origin, selector) {
223
+ if (origin == null || selector == null) {
224
+ return;
225
+ }
226
+ function matches(element) {
227
+ return typeof selector === "string" ? element.matches?.(selector) ?? false : typeof selector === "function" ? selector(element) : false;
228
+ }
229
+ if (matches(origin)) {
230
+ return origin;
231
+ }
232
+ let parent = origin.parentElement;
233
+ while (parent != null && !matches(parent)) {
234
+ if (parent === document.body) {
235
+ return;
236
+ }
237
+ parent = parent.parentElement;
238
+ }
239
+ return parent ?? undefined;
240
+ }
241
+ function getElementUnderPointer(skipIgnore) {
242
+ const elements = Array.from(document.querySelectorAll(":hover")).filter((element) => {
243
+ if (/^head$/i.test(element.tagName)) {
244
+ return false;
245
+ }
246
+ const style = getComputedStyle(element);
247
+ return typeof skipIgnore === "boolean" && skipIgnore || style.pointerEvents !== "none" && style.visibility !== "hidden";
248
+ });
249
+ return elements[elements.length - 1];
250
+ }
251
+ function getTextDirection(element) {
252
+ const direction = element.getAttribute("dir");
253
+ if (direction !== null && /^(ltr|rtl)$/i.test(direction)) {
254
+ return direction.toLowerCase();
255
+ }
256
+ return getComputedStyle?.(element)?.direction === "rtl" ? "rtl" : "ltr";
257
+ }
258
+
259
+ // src/js/event.ts
260
+ function getEventPosition(event) {
261
+ let x;
262
+ let y;
263
+ if (event instanceof MouseEvent) {
264
+ x = event.clientX;
265
+ y = event.clientY;
266
+ } else if (event instanceof TouchEvent) {
267
+ x = event.touches[0]?.clientX;
268
+ y = event.touches[0]?.clientY;
269
+ }
270
+ return typeof x === "number" && typeof y === "number" ? { x, y } : undefined;
271
+ }
158
272
  export {
273
+ wait,
159
274
  setValue,
275
+ repeat,
160
276
  isObject,
161
277
  isNullableOrWhitespace,
162
278
  isNullable,
@@ -164,10 +280,11 @@ export {
164
280
  getValue,
165
281
  getTextDirection,
166
282
  getString,
167
- getPosition,
168
283
  getNumber,
284
+ getEventPosition,
169
285
  getElementUnderPointer,
170
286
  findParentElement,
287
+ findElements,
171
288
  createUuid,
172
289
  clampNumber
173
290
  };
@@ -0,0 +1,35 @@
1
+ // src/js/number.ts
2
+ function clampNumber(value, min, max) {
3
+ return Math.min(Math.max(getNumber(value), getNumber(min)), getNumber(max));
4
+ }
5
+ function getNumber(value) {
6
+ if (typeof value === "number") {
7
+ return value;
8
+ }
9
+ if (typeof value === "symbol") {
10
+ return NaN;
11
+ }
12
+ let parsed = value?.valueOf?.() ?? value;
13
+ if (typeof parsed === "object") {
14
+ parsed = parsed?.toString() ?? parsed;
15
+ }
16
+ if (typeof parsed !== "string") {
17
+ return parsed == null ? NaN : typeof parsed === "number" ? parsed : +parsed;
18
+ }
19
+ if (/^\s*0+\s*$/.test(parsed)) {
20
+ return 0;
21
+ }
22
+ const trimmed = parsed.trim();
23
+ if (trimmed.length === 0) {
24
+ return NaN;
25
+ }
26
+ const isBinary = /^0b[01]+$/i.test(trimmed);
27
+ if (isBinary || /^0o[0-7]+$/i.test(trimmed)) {
28
+ return parseInt(trimmed.slice(2), isBinary ? 2 : 8);
29
+ }
30
+ return +(/^0x[0-9a-f]+$/i.test(trimmed) ? trimmed : trimmed.replace(/_/g, ""));
31
+ }
32
+ export {
33
+ getNumber,
34
+ clampNumber
35
+ };
@@ -0,0 +1,15 @@
1
+ // src/js/string.ts
2
+ function createUuid() {
3
+ return "10000000-1000-4000-8000-100000000000".replace(/[018]/g, (substring) => (substring ^ crypto.getRandomValues(new Uint8Array(1))[0] & 15 >> substring / 4).toString(16));
4
+ }
5
+ function getString(value) {
6
+ return typeof value === "string" ? value : typeof value?.toString === "function" ? value.toString() : String(value);
7
+ }
8
+ function isNullableOrWhitespace(value) {
9
+ return value == null || getString(value).trim().length === 0;
10
+ }
11
+ export {
12
+ isNullableOrWhitespace,
13
+ getString,
14
+ createUuid
15
+ };
@@ -0,0 +1,96 @@
1
+ // src/js/timer.ts
2
+ function repeat(callback, options) {
3
+ const count = typeof options?.count === "number" ? options.count : Infinity;
4
+ return timer(callback, { ...{ count }, ...options ?? {} }).start();
5
+ }
6
+ var timer = function(callback, config) {
7
+ const options = {
8
+ afterCallback: typeof config.afterCallback === "function" ? config.afterCallback : undefined,
9
+ callback,
10
+ count: typeof config.count === "number" && config.count >= 1 ? config.count : 1,
11
+ interval: typeof config.interval === "number" && config.interval >= 0 ? config.interval : 0
12
+ };
13
+ const state = {
14
+ active: false
15
+ };
16
+ const timer2 = Object.create(null);
17
+ Object.defineProperties(timer2, {
18
+ active: {
19
+ get() {
20
+ return state.active;
21
+ }
22
+ },
23
+ restart: {
24
+ value() {
25
+ return work("restart", timer2, state, options);
26
+ }
27
+ },
28
+ start: {
29
+ value() {
30
+ return work("start", timer2, state, options);
31
+ }
32
+ },
33
+ stop: {
34
+ value() {
35
+ return work("stop", timer2, state, options);
36
+ }
37
+ }
38
+ });
39
+ return timer2;
40
+ };
41
+ function wait(callback, time) {
42
+ return timer(callback, {
43
+ count: 1,
44
+ interval: time
45
+ }).start();
46
+ }
47
+ var work = function(type, timer2, state, options) {
48
+ if (type === "start" && timer2.active || type === "stop" && !timer2.active) {
49
+ return timer2;
50
+ }
51
+ const { afterCallback, callback, count, interval } = options;
52
+ if (typeof state.frame === "number") {
53
+ cancelAnimationFrame(state.frame);
54
+ afterCallback?.(false);
55
+ }
56
+ if (type === "stop") {
57
+ state.active = false;
58
+ state.frame = undefined;
59
+ return timer2;
60
+ }
61
+ state.active = true;
62
+ const isRepeated = count > 0;
63
+ const milliseconds = 16.666666666666668;
64
+ let index = 0;
65
+ let start;
66
+ function step(timestamp) {
67
+ if (!state.active) {
68
+ return;
69
+ }
70
+ start ??= timestamp;
71
+ const elapsed = timestamp - start;
72
+ const maximum = elapsed + milliseconds;
73
+ const minimum = elapsed - milliseconds;
74
+ if (minimum < interval && interval < maximum) {
75
+ if (state.active) {
76
+ callback(isRepeated ? index : undefined);
77
+ }
78
+ index += 1;
79
+ if (index < count) {
80
+ start = undefined;
81
+ } else {
82
+ state.active = false;
83
+ state.frame = undefined;
84
+ afterCallback?.(true);
85
+ return;
86
+ }
87
+ }
88
+ state.frame = requestAnimationFrame(step);
89
+ }
90
+ state.frame = requestAnimationFrame(step);
91
+ return timer2;
92
+ };
93
+ export {
94
+ wait,
95
+ repeat
96
+ };
@@ -0,0 +1,84 @@
1
+ // src/js/string.ts
2
+ function createUuid() {
3
+ return "10000000-1000-4000-8000-100000000000".replace(/[018]/g, (substring) => (substring ^ crypto.getRandomValues(new Uint8Array(1))[0] & 15 >> substring / 4).toString(16));
4
+ }
5
+ function getString(value) {
6
+ return typeof value === "string" ? value : typeof value?.toString === "function" ? value.toString() : String(value);
7
+ }
8
+ function isNullableOrWhitespace(value) {
9
+ return value == null || getString(value).trim().length === 0;
10
+ }
11
+
12
+ // src/js/value.ts
13
+ var _getValue = function(data, key) {
14
+ if (typeof data !== "object" || data === null || /^(__proto__|constructor|prototype)$/i.test(key)) {
15
+ return;
16
+ }
17
+ if (data instanceof Map) {
18
+ return data.get(key);
19
+ }
20
+ return data[key];
21
+ };
22
+ var _setValue = function(data, key, value) {
23
+ if (typeof data !== "object" || data === null || /^(__proto__|constructor|prototype)$/i.test(key)) {
24
+ return;
25
+ }
26
+ if (data instanceof Map) {
27
+ data.set(key, value);
28
+ } else {
29
+ data[key] = value;
30
+ }
31
+ };
32
+ function getValue(data, key) {
33
+ if (typeof data !== "object" || data === null || isNullableOrWhitespace(key)) {
34
+ return;
35
+ }
36
+ const parts = getString(key).split(".").reverse();
37
+ let position = parts.length;
38
+ let value = data;
39
+ while (position--) {
40
+ value = _getValue(value, parts[position]);
41
+ if (value == null) {
42
+ break;
43
+ }
44
+ }
45
+ return value;
46
+ }
47
+ function isArrayOrObject(value) {
48
+ return /^(array|object)$/i.test(value?.constructor?.name);
49
+ }
50
+ function isNullable(value) {
51
+ return value == null;
52
+ }
53
+ function isObject(value) {
54
+ return value?.constructor?.name === "Object";
55
+ }
56
+ function setValue(data, key, value) {
57
+ if (typeof data !== "object" || data === null || isNullableOrWhitespace(key)) {
58
+ return data;
59
+ }
60
+ const parts = getString(key).split(".").reverse();
61
+ let position = parts.length;
62
+ let target = data;
63
+ while (position--) {
64
+ const key2 = parts[position];
65
+ if (position === 0) {
66
+ _setValue(target, key2, value);
67
+ break;
68
+ }
69
+ let next = _getValue(target, key2);
70
+ if (typeof next !== "object" || next === null) {
71
+ next = /^\d+$/.test(parts[position - 1]) ? [] : {};
72
+ target[key2] = next;
73
+ }
74
+ target = next;
75
+ }
76
+ return data;
77
+ }
78
+ export {
79
+ setValue,
80
+ isObject,
81
+ isNullable,
82
+ isArrayOrObject,
83
+ getValue
84
+ };
package/package.json CHANGED
@@ -14,40 +14,66 @@
14
14
  "exports": {
15
15
  ".": {
16
16
  "types": "./types/index.d.ts",
17
- "import": "./dist/js/atoms.js"
17
+ "import": "./dist/js/index.js"
18
+ },
19
+ "./element": {
20
+ "types": "./types/element/index.d.ts",
21
+ "import": "./dist/js/element.index.js"
22
+ },
23
+ "./event": {
24
+ "types": "./types/event.d.ts",
25
+ "import": "./dist/js/event.js"
18
26
  },
19
27
  "./focusable": {
20
- "types": "./types/focusable.d.ts",
28
+ "types": "./types/element/focusable.d.ts",
21
29
  "import": "./dist/js/focusable.js"
22
30
  },
31
+ "./number": {
32
+ "types": "./types/number.d.ts",
33
+ "import": "./dist/js/number.js"
34
+ },
35
+ "./string": {
36
+ "types": "./types/string.d.ts",
37
+ "import": "./dist/js/string.js"
38
+ },
23
39
  "./supports-touch": {
24
40
  "types": "./types/touch.d.ts",
25
41
  "import": "./dist/js/touch.js"
26
42
  },
43
+ "./timer": {
44
+ "types": "./types/timer.d.ts",
45
+ "import": "./dist/js/timer.js"
46
+ },
47
+ "./value": {
48
+ "types": "./types/value.d.ts",
49
+ "import": "./dist/js/value.js"
50
+ },
27
51
  "./package.json": "./package.json"
28
52
  },
29
- "files": ["dist", "src", "types"],
53
+ "files": [
54
+ "dist",
55
+ "src",
56
+ "types"
57
+ ],
30
58
  "keywords": [],
31
59
  "license": "MIT",
32
- "main": "./dist/js/atoms.js",
33
- "module": "./dist/js/atoms.js",
60
+ "main": "./dist/js/index.js",
61
+ "module": "./dist/js/index.js",
34
62
  "name": "@oscarpalmer/atoms",
35
63
  "repository": {
36
64
  "type": "git",
37
65
  "url": "git+https://github.com/oscarpalmer/atoms.git"
38
66
  },
39
67
  "scripts": {
40
- "build": "bun run build:css && bun run build:js && bun run build:js-focusable && bun run build:js-touch && bun run types",
68
+ "build": "bun run build:css && bun run build:js && bun run types",
41
69
  "build:css": "bunx sass ./src/css:./dist/css --no-source-map",
42
- "build:js": "bunx bun build ./src/js/index.ts --outfile ./dist/js/atoms.js",
43
- "build:js-focusable": "bunx bun build ./src/js/element/focusable.ts --outfile ./dist/js/focusable.js",
44
- "build:js-touch": "bunx bun build ./src/js/touch.ts --outfile ./dist/js/touch.js",
70
+ "build:js": "bunx bun ./.bun.ts",
45
71
  "test": "bun test --preload ./test/_preload.ts --coverage",
46
72
  "types": "bunx tsc -p ./tsconfig.json",
47
73
  "watch:css": "bunx sass ./src/css:./dist/css --no-source-map --watch",
48
- "watch:js": "bun build ./src/js/index.ts --outfile ./dist/js/atoms.js --watch"
74
+ "watch:js": "bun build ./src/js/index.ts --outfile ./dist/js/index.js --watch"
49
75
  },
50
76
  "type": "module",
51
77
  "types": "./types/index.d.ts",
52
- "version": "0.9.0"
78
+ "version": "0.10.0"
53
79
  }
@@ -1,8 +1,50 @@
1
+ type Selector = string | Element | Element[] | NodeList;
2
+
1
3
  type TextDirection = 'ltr' | 'rtl';
2
4
 
5
+ /**
6
+ * - Find elements that match the selector
7
+ * - `context` is optional and defaults to `document`
8
+ */
9
+ export function findElements(
10
+ selector: Selector,
11
+ context?: Selector,
12
+ ): Element[] {
13
+ const contexts = context === undefined ? [document] : findElements(context);
14
+
15
+ const elements: Element[] = [];
16
+
17
+ if (typeof selector === 'string') {
18
+ for (const context of contexts) {
19
+ elements.push(
20
+ ...Array.from((context as Element).querySelectorAll(selector) ?? []),
21
+ );
22
+ }
23
+
24
+ return elements;
25
+ }
26
+
27
+ const nodes =
28
+ Array.isArray(selector) || selector instanceof NodeList
29
+ ? selector
30
+ : [selector];
31
+
32
+ for (const node of nodes) {
33
+ if (
34
+ node instanceof Element &&
35
+ contexts.some(context => context.contains(node))
36
+ ) {
37
+ elements.push(node);
38
+ }
39
+ }
40
+
41
+ return elements;
42
+ }
43
+
3
44
  /**
4
45
  * - Find the parent element that matches the selector
5
46
  * - Matches may be found by a query string or a callback
47
+ * - If no match is found, `undefined` is returned
6
48
  */
7
49
  export function findParentElement(
8
50
  origin: Element,
package/src/js/event.ts CHANGED
@@ -6,7 +6,7 @@ type Position = {
6
6
  /**
7
7
  * Get the X- and Y-coordinates from a pointer event
8
8
  */
9
- export function getPosition(
9
+ export function getEventPosition(
10
10
  event: MouseEvent | TouchEvent,
11
11
  ): Position | undefined {
12
12
  let x: number | undefined;
package/src/js/index.ts CHANGED
@@ -2,4 +2,5 @@ export * from './element';
2
2
  export * from './event';
3
3
  export * from './number';
4
4
  export * from './string';
5
+ export * from './timer';
5
6
  export * from './value';
@@ -0,0 +1,207 @@
1
+ /**
2
+ * Callback that runs after the timer has finished (or is stopped)
3
+ * - `finished` is `true` if the timer was allowed to finish, and `false` if it was stopped
4
+ */
5
+ type AfterCallback = (finished: boolean) => void;
6
+
7
+ /**
8
+ * Callback that runs for each iteration of the timer
9
+ */
10
+ type IndexedCallback = (index: number) => void;
11
+
12
+ type Options = {
13
+ /**
14
+ * Callback to run after the timer has finished (or is stopped)
15
+ * - `finished` is `true` if the timer was allowed to finish, and `false` if it was stopped
16
+ */
17
+ afterCallback?: AfterCallback;
18
+ /**
19
+ * How many times the timer should repeat
20
+ */
21
+ count?: number;
22
+ /**
23
+ * Interval between each callback
24
+ */
25
+ interval?: number;
26
+ };
27
+
28
+ type Timer = {
29
+ /**
30
+ * Is the timer running?
31
+ */
32
+ get active(): boolean;
33
+ /**
34
+ * Restarts the timer
35
+ */
36
+ restart: () => Timer;
37
+ /**
38
+ * Starts the timer
39
+ */
40
+ start: () => Timer;
41
+ /**
42
+ * Stops the timer
43
+ */
44
+ stop: () => Timer;
45
+ };
46
+
47
+ type TimerOptions = {
48
+ afterCallback?: AfterCallback;
49
+ callback: IndexedCallback;
50
+ count: number;
51
+ interval: number;
52
+ };
53
+
54
+ type TimerState = {
55
+ active: boolean;
56
+ frame?: number;
57
+ };
58
+
59
+ type WorkType = 'restart' | 'start' | 'stop';
60
+
61
+ /**
62
+ * - Creates a timer which calls a callback after a certain amount of time, and repeats it a certain amount of times, with an optional callback after it's finished (or stopped)
63
+ * - `options.count` defaults to `Infinity`
64
+ * - `options.time` defaults to `0`
65
+ * - `options.afterCallback` defaults to `undefined`
66
+ */
67
+ export function repeat(
68
+ callback: (index: number) => void,
69
+ options?: Options,
70
+ ): Timer {
71
+ const count = typeof options?.count === 'number' ? options.count : Infinity;
72
+
73
+ return timer(callback, {...{count}, ...(options ?? {})}).start();
74
+ }
75
+
76
+ function timer(callback: IndexedCallback, config: Options): Timer {
77
+ const options: TimerOptions = {
78
+ afterCallback:
79
+ typeof config.afterCallback === 'function'
80
+ ? config.afterCallback
81
+ : undefined,
82
+ callback,
83
+ count:
84
+ typeof config.count === 'number' && config.count >= 1 ? config.count : 1,
85
+ interval:
86
+ typeof config.interval === 'number' && config.interval >= 0
87
+ ? config.interval
88
+ : 0,
89
+ };
90
+
91
+ const state: TimerState = {
92
+ active: false,
93
+ };
94
+
95
+ const timer = Object.create(null);
96
+
97
+ Object.defineProperties(timer, {
98
+ active: {
99
+ get() {
100
+ return state.active;
101
+ },
102
+ },
103
+ restart: {
104
+ value() {
105
+ return work('restart', timer, state, options);
106
+ },
107
+ },
108
+ start: {
109
+ value() {
110
+ return work('start', timer, state, options);
111
+ },
112
+ },
113
+ stop: {
114
+ value() {
115
+ return work('stop', timer, state, options);
116
+ },
117
+ },
118
+ });
119
+
120
+ return timer;
121
+ }
122
+
123
+ /**
124
+ * - Creates a timer which calls a callback after a certain amount of time
125
+ * - `time` defaults to `0`
126
+ */
127
+ export function wait(callback: () => void, time?: number): Timer {
128
+ return timer(callback, {
129
+ count: 1,
130
+ interval: time,
131
+ }).start();
132
+ }
133
+
134
+ function work(
135
+ type: WorkType,
136
+ timer: Timer,
137
+ state: TimerState,
138
+ options: TimerOptions,
139
+ ): Timer {
140
+ if (
141
+ (type === 'start' && timer.active) ||
142
+ (type === 'stop' && !timer.active)
143
+ ) {
144
+ return timer;
145
+ }
146
+
147
+ const {afterCallback, callback, count, interval} = options;
148
+
149
+ if (typeof state.frame === 'number') {
150
+ cancelAnimationFrame(state.frame);
151
+
152
+ afterCallback?.(false);
153
+ }
154
+
155
+ if (type === 'stop') {
156
+ state.active = false;
157
+ state.frame = undefined;
158
+
159
+ return timer;
160
+ }
161
+
162
+ state.active = true;
163
+
164
+ const isRepeated = count > 0;
165
+ const milliseconds = 1000 / 60;
166
+
167
+ let index = 0;
168
+
169
+ let start: DOMHighResTimeStamp | undefined;
170
+
171
+ function step(timestamp: DOMHighResTimeStamp): void {
172
+ if (!state.active) {
173
+ return;
174
+ }
175
+
176
+ start ??= timestamp;
177
+
178
+ const elapsed = timestamp - start;
179
+ const maximum = elapsed + milliseconds;
180
+ const minimum = elapsed - milliseconds;
181
+
182
+ if (minimum < interval && interval < maximum) {
183
+ if (state.active) {
184
+ callback((isRepeated ? index : undefined) as never);
185
+ }
186
+
187
+ index += 1;
188
+
189
+ if (index < count) {
190
+ start = undefined;
191
+ } else {
192
+ state.active = false;
193
+ state.frame = undefined;
194
+
195
+ afterCallback?.(true);
196
+
197
+ return;
198
+ }
199
+ }
200
+
201
+ state.frame = requestAnimationFrame(step);
202
+ }
203
+
204
+ state.frame = requestAnimationFrame(step);
205
+
206
+ return timer;
207
+ }
@@ -1,7 +1,14 @@
1
+ type Selector = string | Element | Element[] | NodeList;
1
2
  type TextDirection = 'ltr' | 'rtl';
3
+ /**
4
+ * - Find elements that match the selector
5
+ * - `context` is optional and defaults to `document`
6
+ */
7
+ export declare function findElements(selector: Selector, context?: Selector): Element[];
2
8
  /**
3
9
  * - Find the parent element that matches the selector
4
10
  * - Matches may be found by a query string or a callback
11
+ * - If no match is found, `undefined` is returned
5
12
  */
6
13
  export declare function findParentElement(origin: Element, selector: string | ((element: Element) => boolean)): Element | undefined;
7
14
  /**
package/types/event.d.ts CHANGED
@@ -5,5 +5,5 @@ type Position = {
5
5
  /**
6
6
  * Get the X- and Y-coordinates from a pointer event
7
7
  */
8
- export declare function getPosition(event: MouseEvent | TouchEvent): Position | undefined;
8
+ export declare function getEventPosition(event: MouseEvent | TouchEvent): Position | undefined;
9
9
  export {};
package/types/index.d.ts CHANGED
@@ -2,4 +2,5 @@ export * from './element';
2
2
  export * from './event';
3
3
  export * from './number';
4
4
  export * from './string';
5
+ export * from './timer';
5
6
  export * from './value';
@@ -0,0 +1,51 @@
1
+ /**
2
+ * Callback that runs after the timer has finished (or is stopped)
3
+ * - `finished` is `true` if the timer was allowed to finish, and `false` if it was stopped
4
+ */
5
+ type AfterCallback = (finished: boolean) => void;
6
+ type Options = {
7
+ /**
8
+ * Callback to run after the timer has finished (or is stopped)
9
+ * - `finished` is `true` if the timer was allowed to finish, and `false` if it was stopped
10
+ */
11
+ afterCallback?: AfterCallback;
12
+ /**
13
+ * How many times the timer should repeat
14
+ */
15
+ count?: number;
16
+ /**
17
+ * Interval between each callback
18
+ */
19
+ interval?: number;
20
+ };
21
+ type Timer = {
22
+ /**
23
+ * Is the timer running?
24
+ */
25
+ get active(): boolean;
26
+ /**
27
+ * Restarts the timer
28
+ */
29
+ restart: () => Timer;
30
+ /**
31
+ * Starts the timer
32
+ */
33
+ start: () => Timer;
34
+ /**
35
+ * Stops the timer
36
+ */
37
+ stop: () => Timer;
38
+ };
39
+ /**
40
+ * - Creates a timer which calls a callback after a certain amount of time, and repeats it a certain amount of times, with an optional callback after it's finished (or stopped)
41
+ * - `options.count` defaults to `Infinity`
42
+ * - `options.time` defaults to `0`
43
+ * - `options.afterCallback` defaults to `undefined`
44
+ */
45
+ export declare function repeat(callback: (index: number) => void, options?: Options): Timer;
46
+ /**
47
+ * - Creates a timer which calls a callback after a certain amount of time
48
+ * - `time` defaults to `0`
49
+ */
50
+ export declare function wait(callback: () => void, time?: number): Timer;
51
+ export {};
File without changes