@praxisjs/composables 0.1.5 → 1.0.1

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/src/utilities.ts CHANGED
@@ -1,123 +1,174 @@
1
+ import { Composable } from "@praxisjs/core";
1
2
  import { signal, computed } from "@praxisjs/core/internal";
2
3
  import type { Signal, Computed } from "@praxisjs/shared";
3
4
 
4
- export function useClipboard(resetDelay = 2000) {
5
- const copied = signal(false);
6
- const content = signal("");
7
- async function copy(text: string): Promise<void> {
8
- try {
9
- await navigator.clipboard.writeText(text);
10
- content.set(text);
11
- copied.set(true);
12
- setTimeout(() => {
13
- copied.set(false);
14
- }, resetDelay);
15
- } catch {
16
- console.warn("[useClipboard] Falha ao copiar");
17
- }
18
- }
19
- return {
20
- copy,
21
- copied: computed(() => copied()),
22
- content: computed(() => content()),
23
- };
5
+ export interface PaginationOptions {
6
+ total: number;
7
+ pageSize: number;
8
+ initial?: number;
24
9
  }
25
10
 
26
- export function useGeolocation(options?: PositionOptions) {
27
- const lat = signal<number | null>(null);
28
- const lng = signal<number | null>(null);
29
- const error = signal<GeolocationPositionError | null>(null);
30
- const loading = signal(true);
31
- navigator.geolocation.getCurrentPosition(
32
- (pos) => {
33
- lat.set(pos.coords.latitude);
34
- lng.set(pos.coords.longitude);
35
- loading.set(false);
36
- },
37
- (err) => {
38
- error.set(err);
39
- loading.set(false);
40
- },
41
- options,
42
- );
43
- return {
44
- lat: computed(() => lat()),
45
- lng: computed(() => lng()),
46
- error: computed(() => error()),
47
- loading: computed(() => loading()),
48
- };
11
+ export class Clipboard extends Composable {
12
+ declare copied: boolean;
13
+ declare content: string;
14
+ declare copy: (text: string) => Promise<void>;
15
+
16
+ private _resetTimer?: ReturnType<typeof setTimeout>;
17
+
18
+ constructor(private readonly resetDelay = 2000) {
19
+ super();
20
+ }
21
+
22
+ setup() {
23
+ const copied = signal(false);
24
+ const content = signal("");
25
+ const resetDelay = this.resetDelay;
26
+
27
+ const copy = async (text: string): Promise<void> => {
28
+ try {
29
+ await navigator.clipboard.writeText(text);
30
+ content.set(text);
31
+ copied.set(true);
32
+ clearTimeout(this._resetTimer);
33
+ this._resetTimer = setTimeout(() => { copied.set(false); }, resetDelay);
34
+ } catch {
35
+ console.warn("[Clipboard] Falha ao copiar");
36
+ }
37
+ };
38
+
39
+ return { copied, content, copy };
40
+ }
41
+
42
+ onUnmount() {
43
+ clearTimeout(this._resetTimer);
44
+ }
49
45
  }
50
46
 
51
- export function useTimeAgo(
52
- source:
53
- | Signal<Date | number>
54
- | Computed<Date | number>
55
- | (() => Date | number),
56
- locale = "pt-BR",
57
- ): Computed<string> {
58
- const read = typeof source === "function" ? source : source;
59
- const tick = signal(Date.now());
60
- setInterval(() => {
61
- tick.set(Date.now());
62
- }, 60_000);
63
- const rtf = new Intl.RelativeTimeFormat(locale, { numeric: "auto" });
64
-
65
- return computed(() => {
66
- void tick();
67
- const diff = new Date(read() as Date).getTime() - Date.now();
68
- const abs = Math.abs(diff);
69
- if (abs < 60_000) return rtf.format(Math.round(diff / 1000), "second");
70
- if (abs < 3_600_000) return rtf.format(Math.round(diff / 60_000), "minute");
71
- if (abs < 86_400_000)
72
- return rtf.format(Math.round(diff / 3_600_000), "hour");
73
- if (abs < 2_592_000_000)
74
- return rtf.format(Math.round(diff / 86_400_000), "day");
75
- return rtf.format(Math.round(diff / 2_592_000_000), "month");
76
- });
47
+ export class Geolocation extends Composable {
48
+ declare lat: number | null;
49
+ declare lng: number | null;
50
+ declare error: GeolocationPositionError | null;
51
+ declare loading: boolean;
52
+
53
+ private _active = true;
54
+
55
+ constructor(private readonly options?: PositionOptions) {
56
+ super();
57
+ }
58
+
59
+ setup() {
60
+ const lat = signal<number | null>(null);
61
+ const lng = signal<number | null>(null);
62
+ const error = signal<GeolocationPositionError | null>(null);
63
+ const loading = signal(true);
64
+
65
+ navigator.geolocation.getCurrentPosition(
66
+ (pos) => {
67
+ if (!this._active) return;
68
+ lat.set(pos.coords.latitude);
69
+ lng.set(pos.coords.longitude);
70
+ loading.set(false);
71
+ },
72
+ (err) => {
73
+ if (!this._active) return;
74
+ error.set(err);
75
+ loading.set(false);
76
+ },
77
+ this.options,
78
+ );
79
+
80
+ return { lat, lng, error, loading };
81
+ }
82
+
83
+ onUnmount() {
84
+ this._active = false;
85
+ }
77
86
  }
78
87
 
79
- export interface PaginationOptions {
80
- total: number;
81
- pageSize: number;
82
- initial?: number;
88
+ export class TimeAgo extends Composable {
89
+ declare value: string;
90
+
91
+ private _interval?: ReturnType<typeof setInterval>;
92
+
93
+ constructor(
94
+ private readonly source:
95
+ | Signal<Date | number>
96
+ | Computed<Date | number>
97
+ | (() => Date | number),
98
+ private readonly locale = "pt-BR",
99
+ ) {
100
+ super();
101
+ }
102
+
103
+ setup() {
104
+ const tick = signal(Date.now());
105
+ this._interval = setInterval(() => { tick.set(Date.now()); }, 60_000);
106
+
107
+ const rtf = new Intl.RelativeTimeFormat(this.locale, { numeric: "auto" });
108
+ const read = this.source;
109
+
110
+ const value = computed(() => {
111
+ void tick();
112
+ const diff = new Date(read() as Date).getTime() - Date.now();
113
+ const abs = Math.abs(diff);
114
+ if (abs < 60_000) return rtf.format(Math.round(diff / 1000), "second");
115
+ if (abs < 3_600_000) return rtf.format(Math.round(diff / 60_000), "minute");
116
+ if (abs < 86_400_000) return rtf.format(Math.round(diff / 3_600_000), "hour");
117
+ if (abs < 2_592_000_000) return rtf.format(Math.round(diff / 86_400_000), "day");
118
+ return rtf.format(Math.round(diff / 2_592_000_000), "month");
119
+ });
120
+
121
+ return { value };
122
+ }
123
+
124
+ onUnmount() {
125
+ clearInterval(this._interval);
126
+ }
83
127
  }
84
128
 
85
- export function usePagination(
86
- options: PaginationOptions | (() => PaginationOptions),
87
- ) {
88
- const getOpts = typeof options === "function" ? options : () => options;
89
- const _page = signal(getOpts().initial ?? 1);
90
- const totalPages = computed(() =>
91
- Math.ceil(getOpts().total / getOpts().pageSize),
92
- );
93
- const page = computed(() => Math.min(_page(), totalPages()));
94
- const offset = computed(() => (page() - 1) * getOpts().pageSize);
95
- const hasNext = computed(() => page() < totalPages());
96
- const hasPrev = computed(() => page() > 1);
97
- return {
98
- page,
99
- totalPages,
100
- offset,
101
- hasNext,
102
- hasPrev,
103
- pageSize: computed(() => getOpts().pageSize),
104
- pages: computed(() =>
105
- Array.from({ length: totalPages() }, (_, i) => i + 1),
106
- ),
107
- next() {
108
- if (hasNext()) _page.update((p) => p + 1);
109
- },
110
- prev() {
111
- if (hasPrev()) _page.update((p) => p - 1);
112
- },
113
- goTo(p: number) {
114
- _page.set(Math.max(1, Math.min(p, totalPages())));
115
- },
116
- first() {
117
- _page.set(1);
118
- },
119
- last() {
120
- _page.set(totalPages());
121
- },
122
- };
129
+ export class Pagination extends Composable {
130
+ declare page: number;
131
+ declare totalPages: number;
132
+ declare offset: number;
133
+ declare hasNext: boolean;
134
+ declare hasPrev: boolean;
135
+ declare pageSize: number;
136
+ declare pages: number[];
137
+ declare next: () => void;
138
+ declare prev: () => void;
139
+ declare goTo: (p: number) => void;
140
+ declare first: () => void;
141
+ declare last: () => void;
142
+
143
+ constructor(private readonly options: PaginationOptions) {
144
+ super();
145
+ if (options.pageSize <= 0) {
146
+ throw new Error("pageSize must be greater than 0");
147
+ }
148
+ }
149
+
150
+ setup() {
151
+ const opts = this.options;
152
+ const _page = signal(opts.initial ?? 1);
153
+ const totalPages = computed(() => Math.ceil(opts.total / opts.pageSize));
154
+ const page = computed(() => Math.min(_page(), totalPages()));
155
+ const offset = computed(() => (page() - 1) * opts.pageSize);
156
+ const hasNext = computed(() => page() < totalPages());
157
+ const hasPrev = computed(() => page() > 1);
158
+
159
+ return {
160
+ page,
161
+ totalPages,
162
+ offset,
163
+ hasNext,
164
+ hasPrev,
165
+ pageSize: computed(() => opts.pageSize),
166
+ pages: computed(() => Array.from({ length: totalPages() }, (_, i) => i + 1)),
167
+ next: () => { if (hasNext()) _page.update((p) => p + 1); },
168
+ prev: () => { if (hasPrev()) _page.update((p) => p - 1); },
169
+ goTo: (p: number) => { _page.set(Math.max(1, Math.min(p, totalPages()))); },
170
+ first: () => { _page.set(1); },
171
+ last: () => { _page.set(totalPages()); },
172
+ };
173
+ }
123
174
  }