@bgord/ui 0.5.2 → 0.5.5

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.
@@ -1,2 +1,3 @@
1
1
  export * from "./button";
2
2
  export * from "./dialog";
3
+ export * from "./revalidate-on-focus";
@@ -0,0 +1 @@
1
+ export declare function RevalidateOnFocus(): null;
package/dist/index.js CHANGED
@@ -1,600 +1 @@
1
- // src/components/button.tsx
2
- import { jsxDEV } from "react/jsx-dev-runtime";
3
- function Button() {
4
- return /* @__PURE__ */ jsxDEV("button", {
5
- type: "button",
6
- children: "Click"
7
- }, undefined, false, undefined, this);
8
- }
9
- // src/components/dialog.tsx
10
- import { useEffect as useEffect6, useRef as useRef3 } from "react";
11
-
12
- // src/hooks/use-click-outside.ts
13
- import { useEffect } from "react";
14
- function useClickOutside(ref, handler) {
15
- useEffect(() => {
16
- if (typeof document === "undefined")
17
- return;
18
- function listener(event) {
19
- const el = ref.current;
20
- if (!el)
21
- return;
22
- if (el.contains(event.target)) {
23
- if (event.target === el) {
24
- const { left, right, top, bottom } = el.getBoundingClientRect();
25
- const clientX = event instanceof MouseEvent ? event.clientX : event.touches[0].clientX;
26
- const clientY = event instanceof MouseEvent ? event.clientY : event.touches[0].clientY;
27
- const isInRect = clientX >= left && clientX <= right && clientY >= top && clientY <= bottom;
28
- if (isInRect)
29
- return;
30
- } else {
31
- return;
32
- }
33
- }
34
- handler(event);
35
- }
36
- document.addEventListener("mousedown", listener);
37
- document.addEventListener("touchstart", listener);
38
- return () => {
39
- document.removeEventListener("mousedown", listener);
40
- document.removeEventListener("touchstart", listener);
41
- };
42
- }, [ref, handler]);
43
- }
44
- // src/hooks/use-client-filter.ts
45
- import { useCallback, useMemo } from "react";
46
-
47
- // src/services/field.ts
48
- class Field {
49
- static emptyValue = undefined;
50
- static isEmpty(value) {
51
- return value === undefined || value === "" || value === null;
52
- }
53
- static compare(one, another) {
54
- if (Field.isEmpty(one) && Field.isEmpty(another)) {
55
- return true;
56
- }
57
- return one === another;
58
- }
59
- value = Field.emptyValue;
60
- constructor(value) {
61
- this.value = Field.isEmpty(value) ? Field.emptyValue : value;
62
- }
63
- get() {
64
- return this.value;
65
- }
66
- isEmpty() {
67
- return Field.isEmpty(this.value);
68
- }
69
- }
70
-
71
- // src/hooks/use-field.ts
72
- import { useEffect as useEffect2, useState } from "react";
73
- import { useSearchParams } from "react-router";
74
- var useFieldStrategyEnum;
75
- ((useFieldStrategyEnum2) => {
76
- useFieldStrategyEnum2["params"] = "params";
77
- useFieldStrategyEnum2["local"] = "local";
78
- })(useFieldStrategyEnum ||= {});
79
- function useField(config) {
80
- const strategy = config.strategy ?? "local" /* local */;
81
- const [params, setParams] = useSearchParams();
82
- const givenValue = new Field(params.get(config.name));
83
- const defaultValue = new Field(config.defaultValue);
84
- const [currentValue, _setCurrentValue] = useState(givenValue.isEmpty() ? defaultValue.get() : givenValue.get());
85
- const setCurrentValue = (value2) => {
86
- const candidate = new Field(value2);
87
- _setCurrentValue(candidate.get());
88
- };
89
- useEffect2(() => {
90
- const current = new Field(currentValue);
91
- if (strategy === "params" /* params */) {
92
- if (current.isEmpty()) {
93
- params.delete(config.name);
94
- setParams(params);
95
- } else {
96
- params.set(config.name, current.get());
97
- setParams(params);
98
- }
99
- }
100
- if (strategy === "local" /* local */) {}
101
- }, [currentValue, params, setParams, config.name, strategy]);
102
- const value = Field.isEmpty(currentValue) ? "" : currentValue;
103
- const onChange = (event) => setCurrentValue(event.currentTarget.value);
104
- return {
105
- strategy,
106
- defaultValue: defaultValue.get(),
107
- currentValue,
108
- value,
109
- set: setCurrentValue,
110
- handleChange: onChange,
111
- clear: () => setCurrentValue(defaultValue.get()),
112
- label: { props: { htmlFor: config.name } },
113
- input: { props: { id: config.name, name: config.name, value, onChange } },
114
- changed: !Field.compare(currentValue, defaultValue.get()),
115
- unchanged: Field.compare(currentValue, defaultValue.get()),
116
- empty: Field.isEmpty(currentValue)
117
- };
118
- }
119
-
120
- class LocalFields {
121
- static clearAll(fields) {
122
- return () => fields.forEach((field) => field.clear());
123
- }
124
- }
125
-
126
- // src/hooks/use-client-filter.ts
127
- function useClientFilter(config) {
128
- const query = useField({
129
- ...config,
130
- strategy: "local" /* local */
131
- });
132
- const defaultFilterFn = useCallback((given) => {
133
- if (query.empty)
134
- return true;
135
- return Field.compare(given, query.currentValue);
136
- }, [query.empty, query.currentValue]);
137
- const filterFn = useMemo(() => config.filterFn ?? defaultFilterFn, [config.filterFn, defaultFilterFn]);
138
- const options = useMemo(() => Object.entries(config.enum).map(([name, value]) => ({ name, value })), [config.enum]);
139
- return useMemo(() => ({
140
- ...query,
141
- filterFn,
142
- options,
143
- strategy: "local" /* local */
144
- }), [query, filterFn, options]);
145
- }
146
- // src/hooks/use-exit-action.ts
147
- import React from "react";
148
- function useExitAction(options) {
149
- const [phase, setPhase] = React.useState("idle" /* idle */);
150
- const trigger = (event) => {
151
- event.preventDefault();
152
- if (phase === "idle")
153
- setPhase("exiting" /* exiting */);
154
- };
155
- const onAnimationEnd = (event) => {
156
- if (event.animationName !== options.animation)
157
- return;
158
- options.actionFn();
159
- setPhase("gone" /* gone */);
160
- };
161
- const attach = phase === "exiting" ? { "data-animation": options.animation, onAnimationEnd } : undefined;
162
- return { visible: phase !== "gone", attach, trigger };
163
- }
164
- // src/hooks/use-focus-shortcut.ts
165
- import { useCallback as useCallback2, useMemo as useMemo3, useRef } from "react";
166
-
167
- // src/hooks/use-shortcuts.ts
168
- import { useEffect as useEffect3, useMemo as useMemo2 } from "react";
169
- import { tinykeys } from "tinykeys";
170
- function useKeyboardShortcuts(config, options) {
171
- const enabled = options?.enabled ?? true;
172
- const memoizedConfig = useMemo2(() => config, [JSON.stringify(Object.keys(config))]);
173
- useEffect3(() => {
174
- if (!enabled)
175
- return;
176
- const unsubscribe = tinykeys(window, memoizedConfig);
177
- return () => unsubscribe();
178
- }, [memoizedConfig, enabled]);
179
- }
180
-
181
- // src/hooks/use-focus-shortcut.ts
182
- function useFocusKeyboardShortcut(shortcut) {
183
- const ref = useRef(null);
184
- const handleFocus = useCallback2(() => {
185
- if (ref.current) {
186
- ref.current.focus();
187
- }
188
- }, []);
189
- useKeyboardShortcuts({ [shortcut]: handleFocus });
190
- return useMemo3(() => ({ ref }), []);
191
- }
192
- // src/hooks/use-hover.ts
193
- import { useCallback as useCallback3, useRef as useRef2 } from "react";
194
-
195
- // src/hooks/use-toggle.ts
196
- import { useState as useState2 } from "react";
197
- function useToggle({ name, defaultValue = false }) {
198
- const [on, setOn] = useState2(defaultValue);
199
- const enable = () => setOn(true);
200
- const disable = () => setOn(false);
201
- const toggle = () => setOn((v) => !v);
202
- const off = !on;
203
- const props = {
204
- controller: {
205
- "aria-expanded": on ? "true" : "false",
206
- "aria-controls": name,
207
- role: "button",
208
- tabIndex: 0
209
- },
210
- target: {
211
- id: name,
212
- role: "region",
213
- "aria-hidden": on ? "false" : "true"
214
- }
215
- };
216
- return { on, off, enable, disable, toggle, props };
217
- }
218
- function extractUseToggle(_props) {
219
- const { on, off, enable, disable, toggle, props, ...rest } = _props;
220
- return {
221
- toggle: { on, off, enable, disable, toggle, props },
222
- rest
223
- };
224
- }
225
-
226
- // src/hooks/use-hover.ts
227
- function useHover({
228
- enabled = true
229
- } = {}) {
230
- const { on: isOn, enable, disable } = useToggle({ name: "is-hovering" });
231
- const nodeRef = useRef2(null);
232
- const enterEvent = typeof window !== "undefined" && "PointerEvent" in window ? "pointerenter" : "mouseenter";
233
- const leaveEvent = typeof window !== "undefined" && "PointerEvent" in window ? "pointerleave" : "mouseleave";
234
- const ref = useCallback3((node) => {
235
- const prev = nodeRef.current;
236
- if (prev) {
237
- prev.removeEventListener(enterEvent, enable);
238
- prev.removeEventListener(leaveEvent, disable);
239
- }
240
- nodeRef.current = node;
241
- if (node && enabled) {
242
- node.addEventListener(enterEvent, enable);
243
- node.addEventListener(leaveEvent, disable);
244
- }
245
- }, [enterEvent, leaveEvent, enabled, enable, disable]);
246
- return {
247
- attach: { ref },
248
- isHovering: isOn && enabled
249
- };
250
- }
251
- // src/hooks/use-language-selector.tsx
252
- import Cookies from "js-cookie";
253
- import { useCallback as useCallback5, useEffect as useEffect4 } from "react";
254
- import { useRevalidator } from "react-router";
255
-
256
- // src/services/translations.tsx
257
- import { createContext, use, useCallback as useCallback4 } from "react";
258
-
259
- // src/services/pluralize.ts
260
- import { polishPlurals } from "polish-plurals";
261
- function pluralize(options) {
262
- if (options.language === "en" /* en */) {
263
- const plural = options.plural ?? `${options.singular}s`;
264
- if (options.value === 1)
265
- return options.singular;
266
- return plural;
267
- }
268
- if (options.language === "pl" /* pl */) {
269
- const value = options.value ?? 1;
270
- if (value === 1)
271
- return options.singular;
272
- return polishPlurals(options.singular, String(options.plural), String(options.genitive), value);
273
- }
274
- console.warn(`[@bgord/frontend] missing pluralization function for language: ${options.language}.`);
275
- return options.singular;
276
- }
277
-
278
- // src/services/translations.tsx
279
- var TranslationsContext = createContext({
280
- translations: {},
281
- language: "en"
282
- });
283
- function useTranslations() {
284
- const value = use(TranslationsContext);
285
- if (value === undefined) {
286
- throw new Error("useTranslations must be used within the TranslationsContext");
287
- }
288
- const translate = useCallback4((key, variables) => {
289
- const translation = value.translations[key];
290
- if (!translation) {
291
- console.warn(`[@bgord/ui] missing translation for key: ${key}`);
292
- return key;
293
- }
294
- if (!variables)
295
- return translation;
296
- return Object.entries(variables).reduce((result, [placeholder, value2]) => {
297
- const regex = new RegExp(`{{${placeholder}}}`, "g");
298
- return result.replace(regex, String(value2));
299
- }, translation);
300
- }, [value.translations]);
301
- return translate;
302
- }
303
- function useLanguage() {
304
- const value = use(TranslationsContext);
305
- if (value === undefined) {
306
- throw new Error("useLanguage must be used within the TranslationsContext");
307
- }
308
- return value.language;
309
- }
310
- function usePluralize() {
311
- const language = useLanguage();
312
- return (options) => pluralize({ ...options, language });
313
- }
314
-
315
- // src/hooks/use-language-selector.tsx
316
- function useLanguageSelector(supportedLanguages) {
317
- const language = useLanguage();
318
- const revalidator = useRevalidator();
319
- const field = useClientFilter({
320
- enum: supportedLanguages,
321
- defaultValue: language,
322
- name: "language"
323
- });
324
- const handleLanguageChange = useCallback5(() => {
325
- const current = new Field(field.currentValue);
326
- if (!current.isEmpty() && field.changed) {
327
- Cookies.set("language", String(current.get()));
328
- revalidator.revalidate();
329
- }
330
- }, [field.currentValue, field.changed]);
331
- useEffect4(() => {
332
- handleLanguageChange();
333
- }, [handleLanguageChange]);
334
- return field;
335
- }
336
- // src/hooks/use-meta-enter-submit.tsx
337
- import { useCallback as useCallback6, useMemo as useMemo4 } from "react";
338
- function useMetaEnterSubmit() {
339
- const handleMetaEnterSubmit = useCallback6((event) => {
340
- if (event.key !== "Enter" || !event.metaKey)
341
- return;
342
- event.preventDefault();
343
- event.currentTarget.form?.requestSubmit();
344
- }, []);
345
- return useMemo4(() => ({ onKeyDown: handleMetaEnterSubmit }), [handleMetaEnterSubmit]);
346
- }
347
- // src/hooks/use-scroll-lock.ts
348
- import { useEffect as useEffect5 } from "react";
349
- function useScrollLock(enabled = true) {
350
- useEffect5(() => {
351
- if (typeof document === "undefined")
352
- return;
353
- const originalOverflow = document.body.style.overflow;
354
- if (enabled) {
355
- document.body.style.overflow = "hidden";
356
- }
357
- return () => {
358
- document.body.style.overflow = originalOverflow;
359
- };
360
- }, [enabled]);
361
- }
362
- // src/components/dialog.tsx
363
- import { jsxDEV as jsxDEV2 } from "react/jsx-dev-runtime";
364
- function Dialog(props) {
365
- const { toggle: dialog, rest } = extractUseToggle(props);
366
- const ref = useRef3(null);
367
- useEffect6(() => {
368
- if (props.on) {
369
- ref.current?.showModal();
370
- } else {
371
- ref.current?.close();
372
- }
373
- }, [props.on]);
374
- useKeyboardShortcuts({ Escape: dialog.disable });
375
- useScrollLock(props.on);
376
- useClickOutside(ref, dialog.disable);
377
- return /* @__PURE__ */ jsxDEV2("dialog", {
378
- ref,
379
- tabIndex: 0,
380
- "aria-modal": "true",
381
- "data-disp": props.on ? "flex" : "none",
382
- "data-dir": "column",
383
- "data-mx": "auto",
384
- "data-p": "5",
385
- "data-position": "fixed",
386
- "data-z": "2",
387
- "data-bg": "neutral-900",
388
- "data-br": "xs",
389
- "data-backdrop": "stronger",
390
- "data-animation": "grow-fade-in",
391
- ...rest
392
- }, undefined, false, undefined, this);
393
- }
394
- // src/services/auth-guard.ts
395
- import { redirect } from "react-router";
396
-
397
- // src/services/cookies.ts
398
- class Cookies2 {
399
- static extractFrom(request) {
400
- return request.headers.get("cookie") ?? "";
401
- }
402
- }
403
-
404
- // src/services/auth-guard.ts
405
- class AuthGuard {
406
- API_URL;
407
- constructor(BASE_URL) {
408
- this.API_URL = `${BASE_URL}/api/auth`;
409
- }
410
- async getServerSession(request) {
411
- const cookie = Cookies2.extractFrom(request);
412
- const res = await fetch(`${this.API_URL}/get-session`, {
413
- headers: { cookie, accept: "application/json" }
414
- });
415
- if (!res.ok)
416
- return null;
417
- const session = await res.json();
418
- return session;
419
- }
420
- async requireSession(request) {
421
- const session = await this.getServerSession(request);
422
- if (session?.user)
423
- return session;
424
- throw redirect("/");
425
- }
426
- async requireNoSession(request, target = "/home") {
427
- const session = await this.getServerSession(request);
428
- if (session?.user)
429
- throw redirect(target);
430
- }
431
- async removeSession(request, target = "/login") {
432
- const cookie = Cookies2.extractFrom(request);
433
- const res = await fetch(`${import.meta.env.VITE_API_URL}/api/auth/sign-out`, {
434
- method: "POST",
435
- headers: { cookie }
436
- });
437
- const headers = new Headers;
438
- res.headers.forEach((value, key) => {
439
- if (key.toLowerCase() === "set-cookie")
440
- headers.append("set-cookie", value);
441
- });
442
- throw redirect(target, { headers });
443
- }
444
- }
445
- // src/services/noop.ts
446
- function noop() {}
447
-
448
- // src/services/copy-to-clipboard.ts
449
- var defaultOnCopyToClipboardFailure = () => console.warn("Copying to clipboard not supported");
450
- async function copyToClipboard(options) {
451
- const onFailure = options.onFailure ?? defaultOnCopyToClipboardFailure;
452
- const onSuccess = options.onSuccess ?? noop;
453
- if (!navigator.clipboard)
454
- onFailure();
455
- try {
456
- await navigator.clipboard.writeText(options.text);
457
- onSuccess();
458
- } catch (error) {
459
- onFailure(error);
460
- }
461
- }
462
- // src/services/credentials.ts
463
- var Credentials = {
464
- email: { inputMode: "email", autoComplete: "email", autoCapitalize: "none", spellCheck: "false" },
465
- password: { new: { autoComplete: "new-password" }, current: { autoComplete: "current-password" } }
466
- };
467
- // src/services/etag.ts
468
- class ETag {
469
- static fromRevision(revision) {
470
- return { "if-match": String(revision) };
471
- }
472
- }
473
- // src/services/exec.ts
474
- function exec(list) {
475
- return function() {
476
- for (const item of list) {
477
- item();
478
- }
479
- };
480
- }
481
- // src/services/fields.ts
482
- class Fields {
483
- static allUnchanged(fields) {
484
- return fields.every((field) => field.unchanged);
485
- }
486
- static allEmpty(fields) {
487
- return fields.every((field) => field.empty);
488
- }
489
- static anyEmpty(fields) {
490
- return fields.some((field) => field.empty);
491
- }
492
- static anyUnchanged(fields) {
493
- return fields.some((field) => field.unchanged);
494
- }
495
- static anyChanged(fields) {
496
- return fields.some((field) => field.changed);
497
- }
498
- }
499
- // src/services/form.ts
500
- class Form {
501
- static inputPattern(config) {
502
- const required = config.required ?? true;
503
- if (config.min && !config.max)
504
- return { pattern: `.{${config.min}}`, required };
505
- if (config.min && config.max)
506
- return { pattern: `.{${config.min},${config.max}}`, required };
507
- if (!config.min && config.max)
508
- return { pattern: `.{,${config.max}}`, required };
509
- return { pattern: undefined, required };
510
- }
511
- static textareaPattern(config) {
512
- const required = config.required ?? true;
513
- if (config.min && !config.max)
514
- return { minLength: config.min, required };
515
- if (config.min && config.max)
516
- return { minLength: config.min, maxLength: config.max, required };
517
- if (!config.min && config.max)
518
- return { maxLength: config.max, required };
519
- return { required };
520
- }
521
- }
522
- // src/services/get-safe-window.ts
523
- function getSafeWindow() {
524
- if (typeof window === "undefined")
525
- return;
526
- return window;
527
- }
528
- // src/services/rhythm.ts
529
- var DEFAULT_BASE_PX = 12;
530
- function Rhythm(base = DEFAULT_BASE_PX) {
531
- return {
532
- times(times) {
533
- const result = base * times;
534
- const dimensions = {
535
- height: { height: px(result) },
536
- minHeight: { minHeight: px(result) },
537
- maxHeight: { maxHeight: px(result) },
538
- width: { width: px(result) },
539
- minWidth: { minWidth: px(result) },
540
- maxWidth: { maxWidth: px(result) },
541
- square: { height: px(result), width: px(result) }
542
- };
543
- const style = {
544
- height: { style: { height: px(result) } },
545
- minHeight: { style: { minHeight: px(result) } },
546
- maxHeight: { style: { maxHeight: px(result) } },
547
- width: { style: { width: px(result) } },
548
- minWidth: { style: { minWidth: px(result) } },
549
- maxWidth: { style: { maxWidth: px(result) } },
550
- square: { style: { height: px(result), width: px(result) } }
551
- };
552
- return { px: px(result), raw: result, style, ...dimensions };
553
- }
554
- };
555
- }
556
- function px(number) {
557
- return `${number}px`;
558
- }
559
- // src/services/weak-etag.ts
560
- class WeakETag {
561
- static fromRevision(revision) {
562
- return { "if-match": `W/${revision}` };
563
- }
564
- }
565
- export {
566
- useTranslations,
567
- useToggle,
568
- useScrollLock,
569
- usePluralize,
570
- useMetaEnterSubmit,
571
- useLanguageSelector,
572
- useLanguage,
573
- useKeyboardShortcuts,
574
- useHover,
575
- useFocusKeyboardShortcut,
576
- useFieldStrategyEnum,
577
- useField,
578
- useExitAction,
579
- useClientFilter,
580
- useClickOutside,
581
- pluralize,
582
- noop,
583
- getSafeWindow,
584
- extractUseToggle,
585
- exec,
586
- copyToClipboard,
587
- WeakETag,
588
- TranslationsContext,
589
- Rhythm,
590
- LocalFields,
591
- Form,
592
- Fields,
593
- Field,
594
- ETag,
595
- Dialog,
596
- Credentials,
597
- Cookies2 as Cookies,
598
- Button,
599
- AuthGuard
600
- };
1
+ import{jsx as U}from"react/jsx-runtime";function he(){return U("button",{type:"button",children:"Click"})}import{useEffect as ie,useRef as me}from"react";import{useEffect as q}from"react";function R(e,t){q(()=>{if(typeof document==="undefined")return;function r(n){let o=e.current;if(!o)return;if(o.contains(n.target))if(n.target===o){let{left:i,right:m,top:x,bottom:a}=o.getBoundingClientRect(),p=n instanceof MouseEvent?n.clientX:n.touches[0].clientX,y=n instanceof MouseEvent?n.clientY:n.touches[0].clientY;if(p>=i&&p<=m&&y>=x&&y<=a)return}else return;t(n)}return document.addEventListener("mousedown",r),document.addEventListener("touchstart",r),()=>{document.removeEventListener("mousedown",r),document.removeEventListener("touchstart",r)}},[e,t])}import{useCallback as $,useMemo as h}from"react";class s{static emptyValue=void 0;static isEmpty(e){return e===void 0||e===""||e===null}static compare(e,t){if(s.isEmpty(e)&&s.isEmpty(t))return!0;return e===t}value=s.emptyValue;constructor(e){this.value=s.isEmpty(e)?s.emptyValue:e}get(){return this.value}isEmpty(){return s.isEmpty(this.value)}}import{useEffect as P,useState as M}from"react";import{useSearchParams as W}from"react-router";var w;((r)=>{r.params="params";r.local="local"})(w||={});function g(e){let t=e.strategy??"local",[r,n]=W(),o=new s(r.get(e.name)),i=new s(e.defaultValue),[m,x]=M(o.isEmpty()?i.get():o.get()),a=(c)=>{let k=new s(c);x(k.get())};P(()=>{let c=new s(m);if(t==="params")if(c.isEmpty())r.delete(e.name),n(r);else r.set(e.name,c.get()),n(r)},[m,r,n,e.name,t]);let p=s.isEmpty(m)?"":m,y=(c)=>a(c.currentTarget.value);return{strategy:t,defaultValue:i.get(),currentValue:m,value:p,set:a,handleChange:y,clear:()=>a(i.get()),label:{props:{htmlFor:e.name}},input:{props:{id:e.name,name:e.name,value:p,onChange:y}},changed:!s.compare(m,i.get()),unchanged:s.compare(m,i.get()),empty:s.isEmpty(m)}}class I{static clearAll(e){return()=>e.forEach((t)=>t.clear())}}function l(e){let t=g({...e,strategy:"local"}),r=$((i)=>{if(t.empty)return!0;return s.compare(i,t.currentValue)},[t.empty,t.currentValue]),n=h(()=>e.filterFn??r,[e.filterFn,r]),o=h(()=>Object.entries(e.enum).map(([i,m])=>({name:i,value:m})),[e.enum]);return h(()=>({...t,filterFn:n,options:o,strategy:"local"}),[t,n,o])}import D from"react";function Ie(e){let[t,r]=D.useState("idle"),n=(m)=>{if(m.preventDefault(),t==="idle")r("exiting")},o=(m)=>{if(m.animationName!==e.animation)return;e.actionFn(),r("gone")},i=t==="exiting"?{"data-animation":e.animation,onAnimationEnd:o}:void 0;return{visible:t!=="gone",attach:i,trigger:n}}import{useCallback as B,useMemo as X,useRef as J}from"react";import{useEffect as v,useMemo as K}from"react";import{tinykeys as _}from"tinykeys";function T(e,t){let r=t?.enabled??!0,n=K(()=>e,[JSON.stringify(Object.keys(e))]);v(()=>{if(!r)return;let o=_(window,n);return()=>o()},[n,r])}function Je(e){let t=J(null),r=B(()=>{if(t.current)t.current.focus()},[]);return T({[e]:r}),X(()=>({ref:t}),[])}import{useCallback as N,useRef as Q}from"react";import{useState as O}from"react";function L({name:e,defaultValue:t=!1}){let[r,n]=O(t);return{on:r,off:!r,enable:()=>n(!0),disable:()=>n(!1),toggle:()=>n((p)=>!p),props:{controller:{"aria-expanded":r?"true":"false","aria-controls":e,role:"button",tabIndex:0},target:{id:e,role:"region","aria-hidden":r?"false":"true"}}}}function A(e){let{on:t,off:r,enable:n,disable:o,toggle:i,props:m,...x}=e;return{toggle:{on:t,off:r,enable:n,disable:o,toggle:i,props:m},rest:x}}function je({enabled:e=!0}={}){let{on:t,enable:r,disable:n}=L({name:"is-hovering"}),o=Q(null),i=typeof window!=="undefined"&&"PointerEvent"in window?"pointerenter":"mouseenter",m=typeof window!=="undefined"&&"PointerEvent"in window?"pointerleave":"mouseleave";return{attach:{ref:N((a)=>{let p=o.current;if(p)p.removeEventListener(i,r),p.removeEventListener(m,n);if(o.current=a,a&&e)a.addEventListener(i,r),a.addEventListener(m,n)},[i,m,e,r,n])},isHovering:t&&e}}import Y from"js-cookie";import{useCallback as z,useEffect as V}from"react";import{useRevalidator as ee}from"react-router";import{createContext as Z,use as b,useCallback as j}from"react";import{polishPlurals as G}from"polish-plurals";function C(e){if(e.language==="en"){let t=e.plural??`${e.singular}s`;if(e.value===1)return e.singular;return t}if(e.language==="pl"){let t=e.value??1;if(t===1)return e.singular;return G(e.singular,String(e.plural),String(e.genitive),t)}return console.warn(`[@bgord/frontend] missing pluralization function for language: ${e.language}.`),e.singular}var F=Z({translations:{},language:"en"});function nt(){let e=b(F);if(e===void 0)throw new Error("useTranslations must be used within the TranslationsContext");return j((r,n)=>{let o=e.translations[r];if(!o)return console.warn(`[@bgord/ui] missing translation for key: ${r}`),r;if(!n)return o;return Object.entries(n).reduce((i,[m,x])=>{let a=new RegExp(`{{${m}}}`,"g");return i.replace(a,String(x))},o)},[e.translations])}function f(){let e=b(F);if(e===void 0)throw new Error("useLanguage must be used within the TranslationsContext");return e.language}function ot(){let e=f();return(t)=>C({...t,language:e})}function yt(e){let t=f(),r=ee(),n=l({enum:e,defaultValue:t,name:"language"}),o=z(()=>{let i=new s(n.currentValue);if(!i.isEmpty()&&n.changed)Y.set("language",String(i.get())),r.revalidate()},[n.currentValue,n.changed]);return V(()=>{o()},[o]),n}import{useCallback as te,useMemo as re}from"react";function ht(){let e=te((t)=>{if(t.key!=="Enter"||!t.metaKey)return;t.preventDefault(),t.currentTarget.form?.requestSubmit()},[]);return re(()=>({onKeyDown:e}),[e])}import{useEffect as ne}from"react";function H(e=!0){ne(()=>{if(typeof document==="undefined")return;let t=document.body.style.overflow;if(e)document.body.style.overflow="hidden";return()=>{document.body.style.overflow=t}},[e])}import{jsx as se}from"react/jsx-runtime";function Pt(e){let{toggle:t,rest:r}=A(e),n=me(null);return ie(()=>{if(e.on)n.current?.showModal();else n.current?.close()},[e.on]),T({Escape:t.disable}),H(e.on),R(n,t.disable),se("dialog",{ref:n,tabIndex:0,"aria-modal":"true","data-disp":e.on?"flex":"none","data-dir":"column","data-mx":"auto","data-p":"5","data-position":"fixed","data-z":"2","data-bg":"neutral-900","data-br":"xs","data-backdrop":"stronger","data-animation":"grow-fade-in",...r})}import{useEffect as ue}from"react";import{useRevalidator as ae}from"react-router";function Dt(){let e=ae();return ue(()=>{let t=()=>e.revalidate();return window.addEventListener("focus",t),()=>window.removeEventListener("focus",t)},[e]),null}import{redirect as E}from"react-router";class d{static extractFrom(e){return e.headers.get("cookie")??""}}class pe{API_URL;constructor(e){this.API_URL=`${e}/api/auth`}async getServerSession(e){let t=d.extractFrom(e),r=await fetch(`${this.API_URL}/get-session`,{headers:{cookie:t,accept:"application/json"}});if(!r.ok)return null;return await r.json()}async requireSession(e){let t=await this.getServerSession(e);if(t?.user)return t;throw E("/")}async requireNoSession(e,t="/home"){if((await this.getServerSession(e))?.user)throw E(t)}async removeSession(e,t="/login"){let r=d.extractFrom(e),n=await fetch(`${import.meta.env.VITE_API_URL}/api/auth/sign-out`,{method:"POST",headers:{cookie:r}}),o=new Headers;throw n.headers.forEach((i,m)=>{if(m.toLowerCase()==="set-cookie")o.append("set-cookie",i)}),E(t,{headers:o})}}function S(){}var xe=()=>console.warn("Copying to clipboard not supported");async function jt(e){let t=e.onFailure??xe,r=e.onSuccess??S;if(!navigator.clipboard)t();try{await navigator.clipboard.writeText(e.text),r()}catch(n){t(n)}}var zt={email:{inputMode:"email",autoComplete:"email",autoCapitalize:"none",spellCheck:"false"},password:{new:{autoComplete:"new-password"},current:{autoComplete:"current-password"}}};class ce{static fromRevision(e){return{"if-match":String(e)}}}function tr(e){return function(){for(let t of e)t()}}class ye{static allUnchanged(e){return e.every((t)=>t.unchanged)}static allEmpty(e){return e.every((t)=>t.empty)}static anyEmpty(e){return e.some((t)=>t.empty)}static anyUnchanged(e){return e.some((t)=>t.unchanged)}static anyChanged(e){return e.some((t)=>t.changed)}}class Te{static inputPattern(e){let t=e.required??!0;if(e.min&&!e.max)return{pattern:`.{${e.min}}`,required:t};if(e.min&&e.max)return{pattern:`.{${e.min},${e.max}}`,required:t};if(!e.min&&e.max)return{pattern:`.{,${e.max}}`,required:t};return{pattern:void 0,required:t}}static textareaPattern(e){let t=e.required??!0;if(e.min&&!e.max)return{minLength:e.min,required:t};if(e.min&&e.max)return{minLength:e.min,maxLength:e.max,required:t};if(!e.min&&e.max)return{maxLength:e.max,required:t};return{required:t}}}function ir(){if(typeof window==="undefined")return;return window}function sr(e=12){return{times(t){let r=e*t,n={height:{height:u(r)},minHeight:{minHeight:u(r)},maxHeight:{maxHeight:u(r)},width:{width:u(r)},minWidth:{minWidth:u(r)},maxWidth:{maxWidth:u(r)},square:{height:u(r),width:u(r)}},o={height:{style:{height:u(r)}},minHeight:{style:{minHeight:u(r)}},maxHeight:{style:{maxHeight:u(r)}},width:{style:{width:u(r)}},minWidth:{style:{minWidth:u(r)}},maxWidth:{style:{maxWidth:u(r)}},square:{style:{height:u(r),width:u(r)}}};return{px:u(r),raw:r,style:o,...n}}}}function u(e){return`${e}px`}class de{static fromRevision(e){return{"if-match":`W/${e}`}}}export{nt as useTranslations,L as useToggle,H as useScrollLock,ot as usePluralize,ht as useMetaEnterSubmit,yt as useLanguageSelector,f as useLanguage,T as useKeyboardShortcuts,je as useHover,Je as useFocusKeyboardShortcut,w as useFieldStrategyEnum,g as useField,Ie as useExitAction,l as useClientFilter,R as useClickOutside,C as pluralize,S as noop,ir as getSafeWindow,A as extractUseToggle,tr as exec,jt as copyToClipboard,de as WeakETag,F as TranslationsContext,sr as Rhythm,Dt as RevalidateOnFocus,I as LocalFields,Te as Form,ye as Fields,s as Field,ce as ETag,Pt as Dialog,zt as Credentials,d as Cookies,he as Button,pe as AuthGuard};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bgord/ui",
3
- "version": "0.5.2",
3
+ "version": "0.5.5",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": {
@@ -17,7 +17,7 @@
17
17
  "react-router": "7.7.1"
18
18
  },
19
19
  "scripts": {
20
- "build:js": "bun build src/index.ts --format esm --outdir dist --packages external --external react --external react-dom --external react/jsx-runtime --external react-router",
20
+ "build:js": "NODE_ENV=production bun build src/index.ts --minify --format esm --outdir dist --packages external --external react --external react-dom --external react/jsx-runtime --external react-router",
21
21
  "build:types": "bunx tsc --emitDeclarationOnly",
22
22
  "build": "bun run build:js && bun run build:types"
23
23
  },
package/readme.md CHANGED
@@ -27,6 +27,7 @@ src/
27
27
  ├── components
28
28
  │   ├── button.tsx
29
29
  │   ├── dialog.tsx
30
+ │   └── revalidate-on-focus.tsx
30
31
  ├── hooks
31
32
  │   ├── use-click-outside.ts
32
33
  │   ├── use-client-filter.ts