@praxisjs/composables 1.2.1 → 1.2.3

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 (52) hide show
  1. package/CHANGELOG.md +25 -0
  2. package/dist/browser.d.ts +41 -18
  3. package/dist/browser.d.ts.map +1 -1
  4. package/dist/browser.js +114 -114
  5. package/dist/browser.js.map +1 -1
  6. package/dist/dom.d.ts +24 -16
  7. package/dist/dom.d.ts.map +1 -1
  8. package/dist/dom.js +75 -84
  9. package/dist/dom.js.map +1 -1
  10. package/dist/list.d.ts +4 -1
  11. package/dist/list.d.ts.map +1 -1
  12. package/dist/list.js +29 -11
  13. package/dist/list.js.map +1 -1
  14. package/dist/upload.d.ts.map +1 -1
  15. package/dist/upload.js +32 -12
  16. package/dist/upload.js.map +1 -1
  17. package/dist/utilities.d.ts +13 -1
  18. package/dist/utilities.d.ts.map +1 -1
  19. package/dist/utilities.js +88 -26
  20. package/dist/utilities.js.map +1 -1
  21. package/package.json +14 -5
  22. package/src/browser.ts +129 -115
  23. package/src/dom.ts +77 -85
  24. package/src/list.ts +29 -10
  25. package/src/upload.ts +30 -11
  26. package/src/utilities.ts +101 -24
  27. package/dist/__tests__/browser.test.d.ts +0 -2
  28. package/dist/__tests__/browser.test.d.ts.map +0 -1
  29. package/dist/__tests__/browser.test.js +0 -350
  30. package/dist/__tests__/browser.test.js.map +0 -1
  31. package/dist/__tests__/dom.test.d.ts +0 -2
  32. package/dist/__tests__/dom.test.d.ts.map +0 -1
  33. package/dist/__tests__/dom.test.js +0 -463
  34. package/dist/__tests__/dom.test.js.map +0 -1
  35. package/dist/__tests__/list.test.d.ts +0 -2
  36. package/dist/__tests__/list.test.d.ts.map +0 -1
  37. package/dist/__tests__/list.test.js +0 -156
  38. package/dist/__tests__/list.test.js.map +0 -1
  39. package/dist/__tests__/upload.test.d.ts +0 -2
  40. package/dist/__tests__/upload.test.d.ts.map +0 -1
  41. package/dist/__tests__/upload.test.js +0 -531
  42. package/dist/__tests__/upload.test.js.map +0 -1
  43. package/dist/__tests__/utilities.test.d.ts +0 -2
  44. package/dist/__tests__/utilities.test.d.ts.map +0 -1
  45. package/dist/__tests__/utilities.test.js +0 -251
  46. package/dist/__tests__/utilities.test.js.map +0 -1
  47. package/src/__tests__/browser.test.ts +0 -414
  48. package/src/__tests__/dom.test.ts +0 -567
  49. package/src/__tests__/list.test.ts +0 -210
  50. package/src/__tests__/upload.test.ts +0 -639
  51. package/src/__tests__/utilities.test.ts +0 -327
  52. package/tsconfig.json +0 -8
package/dist/utilities.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { Composable } from "@praxisjs/core";
2
- import { signal, computed } from "@praxisjs/core/internal";
2
+ import { computed, createEffect, disposeEffect, endBatch, signal, startBatch, } from "@praxisjs/core/internal";
3
3
  export class Clipboard extends Composable {
4
4
  resetDelay;
5
5
  _resetTimer;
@@ -14,8 +14,10 @@ export class Clipboard extends Composable {
14
14
  const copy = async (text) => {
15
15
  try {
16
16
  await navigator.clipboard.writeText(text);
17
+ startBatch();
17
18
  content.set(text);
18
19
  copied.set(true);
20
+ endBatch();
19
21
  clearTimeout(this._resetTimer);
20
22
  this._resetTimer = setTimeout(() => { copied.set(false); }, resetDelay);
21
23
  }
@@ -31,66 +33,126 @@ export class Clipboard extends Composable {
31
33
  }
32
34
  export class Geolocation extends Composable {
33
35
  options;
34
- _active = true;
36
+ _lat = signal(null);
37
+ _lng = signal(null);
38
+ _error = signal(null);
39
+ _loading = signal(true);
40
+ _active = false;
35
41
  constructor(options) {
36
42
  super();
37
43
  this.options = options;
38
44
  }
39
45
  setup() {
40
- const lat = signal(null);
41
- const lng = signal(null);
42
- const error = signal(null);
43
- const loading = signal(true);
46
+ return { lat: this._lat, lng: this._lng, error: this._error, loading: this._loading };
47
+ }
48
+ // Requested on mount rather than in setup(), which also runs for components that never mount.
49
+ onMount() {
50
+ if (this._active)
51
+ return;
52
+ this._active = true;
44
53
  navigator.geolocation.getCurrentPosition((pos) => {
45
54
  if (!this._active)
46
55
  return;
47
- lat.set(pos.coords.latitude);
48
- lng.set(pos.coords.longitude);
49
- loading.set(false);
56
+ startBatch();
57
+ this._lat.set(pos.coords.latitude);
58
+ this._lng.set(pos.coords.longitude);
59
+ this._loading.set(false);
60
+ endBatch();
50
61
  }, (err) => {
51
62
  if (!this._active)
52
63
  return;
53
- error.set(err);
54
- loading.set(false);
64
+ startBatch();
65
+ this._error.set(err);
66
+ this._loading.set(false);
67
+ endBatch();
55
68
  }, this.options);
56
- return { lat, lng, error, loading };
57
69
  }
58
70
  onUnmount() {
59
71
  this._active = false;
60
72
  }
61
73
  }
74
+ const SECOND = 1_000;
75
+ const MINUTE = 60_000;
76
+ const HOUR = 3_600_000;
77
+ const DAY = 86_400_000;
78
+ const MONTH = 2_592_000_000;
79
+ // setTimeout fires immediately for delays above 2^31 - 1 ms.
80
+ const MAX_DELAY = 2_147_483_647;
81
+ const UNITS = [
82
+ { below: MINUTE, size: SECOND, name: "second" },
83
+ { below: HOUR, size: MINUTE, name: "minute" },
84
+ { below: DAY, size: HOUR, name: "hour" },
85
+ { below: MONTH, size: DAY, name: "day" },
86
+ { below: Infinity, size: MONTH, name: "month" },
87
+ ];
88
+ function unitIndex(diff) {
89
+ const abs = Math.abs(diff);
90
+ let i = 0;
91
+ while (abs >= UNITS[i].below)
92
+ i++;
93
+ return i;
94
+ }
95
+ /**
96
+ * Milliseconds until the text for `diff` (target − now, shrinking as time passes) changes: the
97
+ * next rounding step within its unit, or the moment it crosses into another unit.
98
+ */
99
+ export function msUntilChange(diff) {
100
+ const i = unitIndex(diff);
101
+ const { size, below } = UNITS[i];
102
+ // Math.round(x) changes once x falls below its value minus one half.
103
+ let wait = diff - (Math.round(diff / size) - 0.5) * size + 1;
104
+ if (diff > 0) {
105
+ if (i > 0)
106
+ wait = Math.min(wait, diff - UNITS[i - 1].below + 1);
107
+ }
108
+ else if (below !== Infinity) {
109
+ wait = Math.min(wait, diff + below);
110
+ }
111
+ return Math.min(Math.max(Math.ceil(wait), 1), MAX_DELAY);
112
+ }
62
113
  export class TimeAgo extends Composable {
63
114
  source;
64
115
  locale;
65
- _interval;
116
+ _tick = signal(0);
117
+ _effect;
66
118
  constructor(source, locale = "pt-BR") {
67
119
  super();
68
120
  this.source = source;
69
121
  this.locale = locale;
70
122
  }
71
123
  setup() {
72
- const tick = signal(Date.now());
73
- this._interval = setInterval(() => { tick.set(Date.now()); }, 60_000);
74
124
  const rtf = new Intl.RelativeTimeFormat(this.locale, { numeric: "auto" });
75
125
  const read = this.source;
126
+ const tick = this._tick;
76
127
  const value = computed(() => {
77
128
  void tick();
78
129
  const diff = new Date(read()).getTime() - Date.now();
79
- const abs = Math.abs(diff);
80
- if (abs < 60_000)
81
- return rtf.format(Math.round(diff / 1000), "second");
82
- if (abs < 3_600_000)
83
- return rtf.format(Math.round(diff / 60_000), "minute");
84
- if (abs < 86_400_000)
85
- return rtf.format(Math.round(diff / 3_600_000), "hour");
86
- if (abs < 2_592_000_000)
87
- return rtf.format(Math.round(diff / 86_400_000), "day");
88
- return rtf.format(Math.round(diff / 2_592_000_000), "month");
130
+ const { size, name } = UNITS[unitIndex(diff)];
131
+ return rtf.format(Math.round(diff / size), name);
89
132
  });
90
133
  return { value };
91
134
  }
135
+ // One timer for the exact moment the text changes, instead of polling: re-armed whenever it
136
+ // fires or the source changes.
137
+ onMount() {
138
+ if (this._effect)
139
+ return;
140
+ const read = this.source;
141
+ const tick = this._tick;
142
+ this._effect = createEffect(() => {
143
+ tick();
144
+ const diff = new Date(read()).getTime() - Date.now();
145
+ // An invalid date has no next change; a NaN delay would fire at once, over and over.
146
+ if (Number.isNaN(diff))
147
+ return;
148
+ const timer = setTimeout(() => { tick.update((n) => n + 1); }, msUntilChange(diff));
149
+ return () => { clearTimeout(timer); };
150
+ });
151
+ }
92
152
  onUnmount() {
93
- clearInterval(this._interval);
153
+ if (this._effect)
154
+ disposeEffect(this._effect);
155
+ this._effect = undefined;
94
156
  }
95
157
  }
96
158
  export class Pagination extends Composable {
@@ -1 +1 @@
1
- {"version":3,"file":"utilities.js","sourceRoot":"","sources":["../src/utilities.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"utilities.js","sourceRoot":"","sources":["../src/utilities.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,7 +1,8 @@
1
1
  {
2
2
  "name": "@praxisjs/composables",
3
- "version": "1.2.1",
3
+ "version": "1.2.3",
4
4
  "type": "module",
5
+ "sideEffects": false,
5
6
  "main": "./dist/index.js",
6
7
  "types": "./dist/index.d.ts",
7
8
  "exports": {
@@ -10,15 +11,23 @@
10
11
  "types": "./dist/index.d.ts"
11
12
  }
12
13
  },
14
+ "files": [
15
+ "dist",
16
+ "src",
17
+ "!dist/**/__tests__",
18
+ "!src/**/__tests__",
19
+ "CHANGELOG.md"
20
+ ],
13
21
  "devDependencies": {
14
22
  "typescript": "^6.0.3"
15
23
  },
16
24
  "dependencies": {
17
- "@praxisjs/core": "2.2.0",
18
- "@praxisjs/shared": "0.3.1"
25
+ "@praxisjs/core": "^2.3.1",
26
+ "@praxisjs/shared": "^0.3.2"
19
27
  },
20
28
  "scripts": {
21
- "build": "tsc",
22
- "dev": "tsc --watch"
29
+ "build": "tsc -p tsconfig.build.json",
30
+ "dev": "tsc -p tsconfig.build.json --watch",
31
+ "typecheck": "tsc --noEmit"
23
32
  }
24
33
  }
package/src/browser.ts CHANGED
@@ -1,33 +1,42 @@
1
1
  import { Composable } from "@praxisjs/core";
2
- import { batch, signal, computed } from "@praxisjs/core/internal";
2
+ import { computed, endBatch, signal, startBatch } from "@praxisjs/core/internal";
3
+
4
+ const hasWindow = typeof window !== "undefined";
5
+
6
+ // Listeners and timers start in onMount and stop in onUnmount: setup() runs when the component is
7
+ // constructed, which also happens for components that never mount (and on the server).
3
8
 
4
9
  export class MediaQuery extends Composable {
5
10
  declare matches: boolean;
6
11
 
7
12
  private _mql?: MediaQueryList;
8
- private _handler?: (e: MediaQueryListEvent) => void;
9
- private _view?: Record<string, unknown>;
13
+ private readonly _matches = signal(false);
14
+
15
+ private readonly _onChange = (e: MediaQueryListEvent) => {
16
+ this._matches.set(e.matches);
17
+ };
10
18
 
11
19
  constructor(private readonly query: string) {
12
20
  super();
13
21
  }
14
22
 
15
23
  setup() {
16
- if (this._view) return this._view;
17
- this._mql = window.matchMedia(this.query);
18
- const matches = signal(this._mql.matches);
19
- this._handler = (e) => {
20
- matches.set(e.matches);
21
- };
22
- this._mql.addEventListener("change", this._handler);
23
- this._view = { matches };
24
- return this._view;
24
+ if (hasWindow) {
25
+ this._mql ??= window.matchMedia(this.query);
26
+ this._matches.set(this._mql.matches);
27
+ }
28
+ return { matches: this._matches };
29
+ }
30
+
31
+ onMount() {
32
+ const mql = this._mql;
33
+ if (!mql) return;
34
+ this._matches.set(mql.matches);
35
+ mql.addEventListener("change", this._onChange);
25
36
  }
26
37
 
27
38
  onUnmount() {
28
- if (this._mql && this._handler) {
29
- this._mql.removeEventListener("change", this._handler);
30
- }
39
+ this._mql?.removeEventListener("change", this._onChange);
31
40
  }
32
41
  }
33
42
 
@@ -35,27 +44,20 @@ export class ColorScheme extends Composable {
35
44
  declare isDark: boolean;
36
45
  declare isLight: boolean;
37
46
 
38
- private _mql?: MediaQueryList;
39
- private _handler?: (e: MediaQueryListEvent) => void;
40
- private _view?: Record<string, unknown>;
47
+ private readonly _query = new MediaQuery("(prefers-color-scheme: dark)");
41
48
 
42
49
  setup() {
43
- if (this._view) return this._view;
44
- this._mql = window.matchMedia("(prefers-color-scheme: dark)");
45
- const isDark = signal(this._mql.matches);
50
+ const isDark = this._query.setup().matches;
46
51
  const isLight = computed(() => !isDark());
47
- this._handler = (e) => {
48
- isDark.set(e.matches);
49
- };
50
- this._mql.addEventListener("change", this._handler);
51
- this._view = { isDark, isLight };
52
- return this._view;
52
+ return { isDark, isLight };
53
+ }
54
+
55
+ onMount() {
56
+ this._query.onMount();
53
57
  }
54
58
 
55
59
  onUnmount() {
56
- if (this._mql && this._handler) {
57
- this._mql.removeEventListener("change", this._handler);
58
- }
60
+ this._query.onUnmount();
59
61
  }
60
62
  }
61
63
 
@@ -63,127 +65,139 @@ export class Mouse extends Composable {
63
65
  declare x: number;
64
66
  declare y: number;
65
67
 
66
- private _handler!: (e: MouseEvent) => void;
67
- private _view?: Record<string, unknown>;
68
+ private readonly _x = signal(0);
69
+ private readonly _y = signal(0);
70
+
71
+ private readonly _onMove = (e: MouseEvent) => {
72
+ startBatch();
73
+ this._x.set(e.clientX);
74
+ this._y.set(e.clientY);
75
+ endBatch();
76
+ };
68
77
 
69
78
  setup() {
70
- if (this._view) return this._view;
71
- const x = signal(0);
72
- const y = signal(0);
73
- this._handler = (e) => {
74
- batch(() => {
75
- x.set(e.clientX);
76
- y.set(e.clientY);
77
- });
78
- };
79
- window.addEventListener("mousemove", this._handler);
80
- this._view = { x, y };
81
- return this._view;
79
+ return { x: this._x, y: this._y };
80
+ }
81
+
82
+ onMount() {
83
+ window.addEventListener("mousemove", this._onMove);
82
84
  }
83
85
 
84
86
  onUnmount() {
85
- window.removeEventListener("mousemove", this._handler);
87
+ window.removeEventListener("mousemove", this._onMove);
86
88
  }
87
89
  }
88
90
 
91
+ const MODIFIERS = ["ctrl", "shift", "alt", "meta"];
92
+
89
93
  export class KeyCombo extends Composable {
90
94
  declare pressed: boolean;
91
95
 
92
- // eslint-disable-next-line @typescript-eslint/no-empty-function
93
- private _keydownHandler = (_e: KeyboardEvent) => {};
94
- private _keyupHandler!: () => void;
95
- private _view?: Record<string, unknown>;
96
+ private readonly _pressed = signal(false);
97
+ // Parsed once: the keydown handler runs for every key press on the page.
98
+ private readonly _key: string | undefined;
99
+ private readonly _ctrl: boolean;
100
+ private readonly _shift: boolean;
101
+ private readonly _alt: boolean;
102
+ private readonly _meta: boolean;
103
+
104
+ private readonly _onKeyDown = (e: KeyboardEvent) => {
105
+ if (
106
+ (!this._ctrl || e.ctrlKey) &&
107
+ (!this._shift || e.shiftKey) &&
108
+ (!this._alt || e.altKey) &&
109
+ (!this._meta || e.metaKey) &&
110
+ e.key.toLowerCase() === this._key
111
+ ) {
112
+ this._pressed.set(true);
113
+ }
114
+ };
115
+
116
+ private readonly _onKeyUp = () => {
117
+ this._pressed.set(false);
118
+ };
96
119
 
97
- constructor(private readonly combo: string) {
120
+ constructor(combo: string) {
98
121
  super();
122
+ const parts = combo.toLowerCase().split("+").map((p) => p.trim());
123
+ this._key = parts.find((p) => !MODIFIERS.includes(p));
124
+ this._ctrl = parts.includes("ctrl");
125
+ this._shift = parts.includes("shift");
126
+ this._alt = parts.includes("alt");
127
+ this._meta = parts.includes("meta");
99
128
  }
100
129
 
101
130
  setup() {
102
- if (this._view) return this._view;
103
- const parts = this.combo
104
- .toLowerCase()
105
- .split("+")
106
- .map((p) => p.trim());
107
- const pressed = signal(false);
108
-
109
- const modifiers = ["ctrl", "shift", "alt", "meta"];
110
- const key = parts.find((p) => !modifiers.includes(p));
111
-
112
- // A combo with no non-modifier key is invalid and should never fire
113
- if (!key) {
114
- this._keyupHandler = () => {
115
- pressed.set(false);
116
- };
117
- window.addEventListener("keydown", this._keydownHandler);
118
- window.addEventListener("keyup", this._keyupHandler);
119
- this._view = { pressed };
120
- return this._view;
121
- }
131
+ return { pressed: this._pressed };
132
+ }
122
133
 
123
- this._keydownHandler = (e) => {
124
- const ctrl = parts.includes("ctrl") ? e.ctrlKey : true;
125
- const shift = parts.includes("shift") ? e.shiftKey : true;
126
- const alt = parts.includes("alt") ? e.altKey : true;
127
- const meta = parts.includes("meta") ? e.metaKey : true;
128
- if (ctrl && shift && alt && meta && e.key.toLowerCase() === key) {
129
- pressed.set(true);
130
- }
131
- };
132
- this._keyupHandler = () => {
133
- pressed.set(false);
134
- };
135
-
136
- window.addEventListener("keydown", this._keydownHandler);
137
- window.addEventListener("keyup", this._keyupHandler);
138
- this._view = { pressed };
139
- return this._view;
134
+ onMount() {
135
+ // A combo with no non-modifier key is invalid and never fires.
136
+ if (this._key !== undefined) window.addEventListener("keydown", this._onKeyDown);
137
+ window.addEventListener("keyup", this._onKeyUp);
140
138
  }
141
139
 
142
140
  onUnmount() {
143
- window.removeEventListener("keydown", this._keydownHandler);
144
- window.removeEventListener("keyup", this._keyupHandler);
141
+ window.removeEventListener("keydown", this._onKeyDown);
142
+ window.removeEventListener("keyup", this._onKeyUp);
145
143
  }
146
144
  }
147
145
 
146
+ const ACTIVITY_EVENTS = ["mousemove", "keydown", "click", "scroll", "touchstart"];
147
+
148
148
  export class Idle extends Composable {
149
149
  declare idle: boolean;
150
150
 
151
- private _listeners: Array<[string, () => void]> = [];
151
+ private readonly _idle = signal(false);
152
152
  private _timer?: ReturnType<typeof setTimeout>;
153
- private _view?: Record<string, unknown>;
153
+ private _lastActivity = 0;
154
+
155
+ // Activity only records a timestamp: rescheduling a timer on every mousemove or scroll event was
156
+ // the expensive part. The single pending timer checks the timestamp when it fires.
157
+ private readonly _onActivity = () => {
158
+ this._lastActivity = Date.now();
159
+ if (this._timer === undefined) {
160
+ this._idle.set(false);
161
+ this._schedule(this.timeout);
162
+ }
163
+ };
164
+
165
+ private readonly _check = () => {
166
+ const remaining = this.timeout - (Date.now() - this._lastActivity);
167
+ if (remaining > 0) {
168
+ this._schedule(remaining);
169
+ } else {
170
+ this._timer = undefined;
171
+ this._idle.set(true);
172
+ }
173
+ };
154
174
 
155
175
  constructor(private readonly timeout = 60_000) {
156
176
  super();
157
177
  }
158
178
 
179
+ private _schedule(delay: number) {
180
+ this._timer = setTimeout(this._check, delay);
181
+ }
182
+
159
183
  setup() {
160
- if (this._view) return this._view;
161
- const idle = signal(false);
162
- const reset = () => {
163
- idle.set(false);
164
- clearTimeout(this._timer);
165
- this._timer = setTimeout(() => {
166
- idle.set(true);
167
- }, this.timeout);
168
- };
169
-
170
- ["mousemove", "keydown", "click", "scroll", "touchstart"].forEach(
171
- (event) => {
172
- window.addEventListener(event, reset, { passive: true });
173
- this._listeners.push([event, reset]);
174
- },
175
- );
176
-
177
- reset();
178
- this._view = { idle };
179
- return this._view;
184
+ return { idle: this._idle };
185
+ }
186
+
187
+ onMount() {
188
+ for (const event of ACTIVITY_EVENTS) {
189
+ window.addEventListener(event, this._onActivity, { passive: true });
190
+ }
191
+ clearTimeout(this._timer);
192
+ this._timer = undefined;
193
+ this._onActivity();
180
194
  }
181
195
 
182
196
  onUnmount() {
183
197
  clearTimeout(this._timer);
184
- this._listeners.forEach(([event, handler]) => {
185
- window.removeEventListener(event, handler);
186
- });
187
- this._listeners = [];
198
+ this._timer = undefined;
199
+ for (const event of ACTIVITY_EVENTS) {
200
+ window.removeEventListener(event, this._onActivity);
201
+ }
188
202
  }
189
203
  }