@bgord/ui 0.4.1 → 0.5.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/dist/hooks/index.d.ts +5 -0
- package/dist/hooks/use-client-filter.d.ts +20 -0
- package/dist/hooks/use-focus-shortcut.d.ts +8 -0
- package/dist/hooks/use-language-selector.d.ts +4 -0
- package/dist/hooks/use-meta-enter-submit.d.ts +3 -0
- package/dist/hooks/use-shortcuts.d.ts +8 -0
- package/dist/index.js +190 -78
- package/dist/services/exec.d.ts +3 -0
- package/dist/services/get-safe-window.d.ts +1 -0
- package/dist/services/index.d.ts +2 -0
- package/package.json +8 -5
- package/readme.md +7 -0
package/dist/hooks/index.d.ts
CHANGED
|
@@ -1,4 +1,9 @@
|
|
|
1
|
+
export * from "./use-client-filter";
|
|
1
2
|
export * from "./use-exit-action";
|
|
2
3
|
export * from "./use-field";
|
|
4
|
+
export * from "./use-focus-shortcut";
|
|
3
5
|
export * from "./use-hover";
|
|
6
|
+
export * from "./use-language-selector";
|
|
7
|
+
export * from "./use-meta-enter-submit";
|
|
8
|
+
export * from "./use-shortcuts";
|
|
4
9
|
export * from "./use-toggle";
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { FieldValueAllowedTypes } from "../services/field";
|
|
2
|
+
import { useFieldConfigType, useFieldReturnType, useFieldStrategyEnum } from "./use-field";
|
|
3
|
+
export type useClientFilterQueryType = string | undefined;
|
|
4
|
+
type useClientFilterConfigType<T extends FieldValueAllowedTypes> = Omit<useFieldConfigType<T>, "strategy"> & {
|
|
5
|
+
enum: {
|
|
6
|
+
[key: string]: useClientFilterQueryType;
|
|
7
|
+
};
|
|
8
|
+
filterFn?: (value: T) => boolean;
|
|
9
|
+
};
|
|
10
|
+
export type useClientFilterReturnType<T extends FieldValueAllowedTypes> = useFieldReturnType<T> & {
|
|
11
|
+
filterFn: NonNullable<useClientFilterConfigType<T>["filterFn"]>;
|
|
12
|
+
options: {
|
|
13
|
+
name: string;
|
|
14
|
+
value: useClientFilterConfigType<T>["enum"][0];
|
|
15
|
+
}[];
|
|
16
|
+
} & {
|
|
17
|
+
strategy: useFieldStrategyEnum.local;
|
|
18
|
+
};
|
|
19
|
+
export declare function useClientFilter<T extends FieldValueAllowedTypes>(config: useClientFilterConfigType<T>): useClientFilterReturnType<T>;
|
|
20
|
+
export {};
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
interface UseKeyboardShortcutsConfigType {
|
|
2
|
+
[keybinding: string]: (event: KeyboardEvent) => void;
|
|
3
|
+
}
|
|
4
|
+
type UseKeyboardShortcutsOptionsType = {
|
|
5
|
+
enabled?: boolean;
|
|
6
|
+
};
|
|
7
|
+
export declare function useKeyboardShortcuts(config: UseKeyboardShortcutsConfigType, options?: UseKeyboardShortcutsOptionsType): void;
|
|
8
|
+
export {};
|
package/dist/index.js
CHANGED
|
@@ -6,27 +6,8 @@ function Button() {
|
|
|
6
6
|
children: "Click"
|
|
7
7
|
}, undefined, false, undefined, this);
|
|
8
8
|
}
|
|
9
|
-
// src/hooks/use-
|
|
10
|
-
import
|
|
11
|
-
function useExitAction(options) {
|
|
12
|
-
const [phase, setPhase] = React.useState("idle" /* idle */);
|
|
13
|
-
const trigger = (event) => {
|
|
14
|
-
event.preventDefault();
|
|
15
|
-
if (phase === "idle")
|
|
16
|
-
setPhase("exiting" /* exiting */);
|
|
17
|
-
};
|
|
18
|
-
const onAnimationEnd = (event) => {
|
|
19
|
-
if (event.animationName !== options.animation)
|
|
20
|
-
return;
|
|
21
|
-
options.actionFn();
|
|
22
|
-
setPhase("gone" /* gone */);
|
|
23
|
-
};
|
|
24
|
-
const attach = phase === "exiting" ? { "data-animation": options.animation, onAnimationEnd } : undefined;
|
|
25
|
-
return { visible: phase !== "gone", attach, trigger };
|
|
26
|
-
}
|
|
27
|
-
// src/hooks/use-field.ts
|
|
28
|
-
import { useEffect, useState } from "react";
|
|
29
|
-
import { useSearchParams } from "react-router";
|
|
9
|
+
// src/hooks/use-client-filter.ts
|
|
10
|
+
import { useCallback, useMemo } from "react";
|
|
30
11
|
|
|
31
12
|
// src/services/field.ts
|
|
32
13
|
class Field {
|
|
@@ -53,6 +34,8 @@ class Field {
|
|
|
53
34
|
}
|
|
54
35
|
|
|
55
36
|
// src/hooks/use-field.ts
|
|
37
|
+
import { useEffect, useState } from "react";
|
|
38
|
+
import { useSearchParams } from "react-router";
|
|
56
39
|
var useFieldStrategyEnum;
|
|
57
40
|
((useFieldStrategyEnum2) => {
|
|
58
41
|
useFieldStrategyEnum2["params"] = "params";
|
|
@@ -104,8 +87,75 @@ class LocalFields {
|
|
|
104
87
|
return () => fields.forEach((field) => field.clear());
|
|
105
88
|
}
|
|
106
89
|
}
|
|
90
|
+
|
|
91
|
+
// src/hooks/use-client-filter.ts
|
|
92
|
+
function useClientFilter(config) {
|
|
93
|
+
const query = useField({
|
|
94
|
+
...config,
|
|
95
|
+
strategy: "local" /* local */
|
|
96
|
+
});
|
|
97
|
+
const defaultFilterFn = useCallback((given) => {
|
|
98
|
+
if (query.empty)
|
|
99
|
+
return true;
|
|
100
|
+
return Field.compare(given, query.currentValue);
|
|
101
|
+
}, [query.empty, query.currentValue]);
|
|
102
|
+
const filterFn = useMemo(() => config.filterFn ?? defaultFilterFn, [config.filterFn, defaultFilterFn]);
|
|
103
|
+
const options = useMemo(() => Object.entries(config.enum).map(([name, value]) => ({ name, value })), [config.enum]);
|
|
104
|
+
return useMemo(() => ({
|
|
105
|
+
...query,
|
|
106
|
+
filterFn,
|
|
107
|
+
options,
|
|
108
|
+
strategy: "local" /* local */
|
|
109
|
+
}), [query, filterFn, options]);
|
|
110
|
+
}
|
|
111
|
+
// src/hooks/use-exit-action.ts
|
|
112
|
+
import React from "react";
|
|
113
|
+
function useExitAction(options) {
|
|
114
|
+
const [phase, setPhase] = React.useState("idle" /* idle */);
|
|
115
|
+
const trigger = (event) => {
|
|
116
|
+
event.preventDefault();
|
|
117
|
+
if (phase === "idle")
|
|
118
|
+
setPhase("exiting" /* exiting */);
|
|
119
|
+
};
|
|
120
|
+
const onAnimationEnd = (event) => {
|
|
121
|
+
if (event.animationName !== options.animation)
|
|
122
|
+
return;
|
|
123
|
+
options.actionFn();
|
|
124
|
+
setPhase("gone" /* gone */);
|
|
125
|
+
};
|
|
126
|
+
const attach = phase === "exiting" ? { "data-animation": options.animation, onAnimationEnd } : undefined;
|
|
127
|
+
return { visible: phase !== "gone", attach, trigger };
|
|
128
|
+
}
|
|
129
|
+
// src/hooks/use-focus-shortcut.ts
|
|
130
|
+
import { useCallback as useCallback2, useMemo as useMemo3, useRef } from "react";
|
|
131
|
+
|
|
132
|
+
// src/hooks/use-shortcuts.ts
|
|
133
|
+
import { useEffect as useEffect2, useMemo as useMemo2 } from "react";
|
|
134
|
+
import { tinykeys } from "tinykeys";
|
|
135
|
+
function useKeyboardShortcuts(config, options) {
|
|
136
|
+
const enabled = options?.enabled ?? true;
|
|
137
|
+
const memoizedConfig = useMemo2(() => config, [JSON.stringify(Object.keys(config))]);
|
|
138
|
+
useEffect2(() => {
|
|
139
|
+
if (!enabled)
|
|
140
|
+
return;
|
|
141
|
+
const unsubscribe = tinykeys(window, memoizedConfig);
|
|
142
|
+
return () => unsubscribe();
|
|
143
|
+
}, [memoizedConfig, enabled]);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// src/hooks/use-focus-shortcut.ts
|
|
147
|
+
function useFocusKeyboardShortcut(shortcut) {
|
|
148
|
+
const ref = useRef(null);
|
|
149
|
+
const handleFocus = useCallback2(() => {
|
|
150
|
+
if (ref.current) {
|
|
151
|
+
ref.current.focus();
|
|
152
|
+
}
|
|
153
|
+
}, []);
|
|
154
|
+
useKeyboardShortcuts({ [shortcut]: handleFocus });
|
|
155
|
+
return useMemo3(() => ({ ref }), []);
|
|
156
|
+
}
|
|
107
157
|
// src/hooks/use-hover.ts
|
|
108
|
-
import { useCallback, useRef } from "react";
|
|
158
|
+
import { useCallback as useCallback3, useRef as useRef2 } from "react";
|
|
109
159
|
|
|
110
160
|
// src/hooks/use-toggle.ts
|
|
111
161
|
import { useState as useState2 } from "react";
|
|
@@ -143,10 +193,10 @@ function useHover({
|
|
|
143
193
|
enabled = true
|
|
144
194
|
} = {}) {
|
|
145
195
|
const { on: isOn, enable, disable } = useToggle({ name: "is-hovering" });
|
|
146
|
-
const nodeRef =
|
|
196
|
+
const nodeRef = useRef2(null);
|
|
147
197
|
const enterEvent = typeof window !== "undefined" && "PointerEvent" in window ? "pointerenter" : "mouseenter";
|
|
148
198
|
const leaveEvent = typeof window !== "undefined" && "PointerEvent" in window ? "pointerleave" : "mouseleave";
|
|
149
|
-
const ref =
|
|
199
|
+
const ref = useCallback3((node) => {
|
|
150
200
|
const prev = nodeRef.current;
|
|
151
201
|
if (prev) {
|
|
152
202
|
prev.removeEventListener(enterEvent, enable);
|
|
@@ -163,6 +213,102 @@ function useHover({
|
|
|
163
213
|
isHovering: isOn && enabled
|
|
164
214
|
};
|
|
165
215
|
}
|
|
216
|
+
// src/hooks/use-language-selector.tsx
|
|
217
|
+
import Cookies from "js-cookie";
|
|
218
|
+
import { useCallback as useCallback5, useEffect as useEffect3 } from "react";
|
|
219
|
+
import { useRevalidator } from "react-router";
|
|
220
|
+
|
|
221
|
+
// src/services/translations.tsx
|
|
222
|
+
import { createContext, use, useCallback as useCallback4 } from "react";
|
|
223
|
+
|
|
224
|
+
// src/services/pluralize.ts
|
|
225
|
+
import { polishPlurals } from "polish-plurals";
|
|
226
|
+
function pluralize(options) {
|
|
227
|
+
if (options.language === "en" /* en */) {
|
|
228
|
+
const plural = options.plural ?? `${options.singular}s`;
|
|
229
|
+
if (options.value === 1)
|
|
230
|
+
return options.singular;
|
|
231
|
+
return plural;
|
|
232
|
+
}
|
|
233
|
+
if (options.language === "pl" /* pl */) {
|
|
234
|
+
const value = options.value ?? 1;
|
|
235
|
+
if (value === 1)
|
|
236
|
+
return options.singular;
|
|
237
|
+
return polishPlurals(options.singular, String(options.plural), String(options.genitive), value);
|
|
238
|
+
}
|
|
239
|
+
console.warn(`[@bgord/frontend] missing pluralization function for language: ${options.language}.`);
|
|
240
|
+
return options.singular;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// src/services/translations.tsx
|
|
244
|
+
var TranslationsContext = createContext({
|
|
245
|
+
translations: {},
|
|
246
|
+
language: "en"
|
|
247
|
+
});
|
|
248
|
+
function useTranslations() {
|
|
249
|
+
const value = use(TranslationsContext);
|
|
250
|
+
if (value === undefined) {
|
|
251
|
+
throw new Error("useTranslations must be used within the TranslationsContext");
|
|
252
|
+
}
|
|
253
|
+
const translate = useCallback4((key, variables) => {
|
|
254
|
+
const translation = value.translations[key];
|
|
255
|
+
if (!translation) {
|
|
256
|
+
console.warn(`[@bgord/ui] missing translation for key: ${key}`);
|
|
257
|
+
return key;
|
|
258
|
+
}
|
|
259
|
+
if (!variables)
|
|
260
|
+
return translation;
|
|
261
|
+
return Object.entries(variables).reduce((result, [placeholder, value2]) => {
|
|
262
|
+
const regex = new RegExp(`{{${placeholder}}}`, "g");
|
|
263
|
+
return result.replace(regex, String(value2));
|
|
264
|
+
}, translation);
|
|
265
|
+
}, [value.translations]);
|
|
266
|
+
return translate;
|
|
267
|
+
}
|
|
268
|
+
function useLanguage() {
|
|
269
|
+
const value = use(TranslationsContext);
|
|
270
|
+
if (value === undefined) {
|
|
271
|
+
throw new Error("useLanguage must be used within the TranslationsContext");
|
|
272
|
+
}
|
|
273
|
+
return value.language;
|
|
274
|
+
}
|
|
275
|
+
function usePluralize() {
|
|
276
|
+
const language = useLanguage();
|
|
277
|
+
return (options) => pluralize({ ...options, language });
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
// src/hooks/use-language-selector.tsx
|
|
281
|
+
function useLanguageSelector(supportedLanguages) {
|
|
282
|
+
const language = useLanguage();
|
|
283
|
+
const revalidator = useRevalidator();
|
|
284
|
+
const field = useClientFilter({
|
|
285
|
+
enum: supportedLanguages,
|
|
286
|
+
defaultValue: language,
|
|
287
|
+
name: "language"
|
|
288
|
+
});
|
|
289
|
+
const handleLanguageChange = useCallback5(() => {
|
|
290
|
+
const current = new Field(field.currentValue);
|
|
291
|
+
if (!current.isEmpty() && field.changed) {
|
|
292
|
+
Cookies.set("language", String(current.get()));
|
|
293
|
+
revalidator.revalidate();
|
|
294
|
+
}
|
|
295
|
+
}, [field.currentValue, field.changed]);
|
|
296
|
+
useEffect3(() => {
|
|
297
|
+
handleLanguageChange();
|
|
298
|
+
}, [handleLanguageChange]);
|
|
299
|
+
return field;
|
|
300
|
+
}
|
|
301
|
+
// src/hooks/use-meta-enter-submit.tsx
|
|
302
|
+
import { useCallback as useCallback6, useMemo as useMemo4 } from "react";
|
|
303
|
+
function useMetaEnterSubmit() {
|
|
304
|
+
const handleMetaEnterSubmit = useCallback6((event) => {
|
|
305
|
+
if (event.key !== "Enter" || !event.metaKey)
|
|
306
|
+
return;
|
|
307
|
+
event.preventDefault();
|
|
308
|
+
event.currentTarget.form?.requestSubmit();
|
|
309
|
+
}, []);
|
|
310
|
+
return useMemo4(() => ({ onKeyDown: handleMetaEnterSubmit }), [handleMetaEnterSubmit]);
|
|
311
|
+
}
|
|
166
312
|
// src/services/colorful.ts
|
|
167
313
|
function Colorful(color) {
|
|
168
314
|
const value = `var(--${color})`;
|
|
@@ -182,6 +328,14 @@ class ETag {
|
|
|
182
328
|
return { "if-match": String(revision) };
|
|
183
329
|
}
|
|
184
330
|
}
|
|
331
|
+
// src/services/exec.ts
|
|
332
|
+
function exec(list) {
|
|
333
|
+
return function() {
|
|
334
|
+
for (const item of list) {
|
|
335
|
+
item();
|
|
336
|
+
}
|
|
337
|
+
};
|
|
338
|
+
}
|
|
185
339
|
// src/services/fields.ts
|
|
186
340
|
class Fields {
|
|
187
341
|
static allUnchanged(fields) {
|
|
@@ -223,23 +377,11 @@ class Form {
|
|
|
223
377
|
return { required };
|
|
224
378
|
}
|
|
225
379
|
}
|
|
226
|
-
// src/services/
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
if (options.value === 1)
|
|
232
|
-
return options.singular;
|
|
233
|
-
return plural;
|
|
234
|
-
}
|
|
235
|
-
if (options.language === "pl" /* pl */) {
|
|
236
|
-
const value = options.value ?? 1;
|
|
237
|
-
if (value === 1)
|
|
238
|
-
return options.singular;
|
|
239
|
-
return polishPlurals(options.singular, String(options.plural), String(options.genitive), value);
|
|
240
|
-
}
|
|
241
|
-
console.warn(`[@bgord/frontend] missing pluralization function for language: ${options.language}.`);
|
|
242
|
-
return options.singular;
|
|
380
|
+
// src/services/get-safe-window.ts
|
|
381
|
+
function getSafeWindow() {
|
|
382
|
+
if (typeof window === "undefined")
|
|
383
|
+
return;
|
|
384
|
+
return window;
|
|
243
385
|
}
|
|
244
386
|
// src/services/rhythm.ts
|
|
245
387
|
var DEFAULT_BASE_PX = 12;
|
|
@@ -272,43 +414,6 @@ function Rhythm(base = DEFAULT_BASE_PX) {
|
|
|
272
414
|
function px(number) {
|
|
273
415
|
return `${number}px`;
|
|
274
416
|
}
|
|
275
|
-
// src/services/translations.tsx
|
|
276
|
-
import { createContext, use, useCallback as useCallback2 } from "react";
|
|
277
|
-
var TranslationsContext = createContext({
|
|
278
|
-
translations: {},
|
|
279
|
-
language: "en"
|
|
280
|
-
});
|
|
281
|
-
function useTranslations() {
|
|
282
|
-
const value = use(TranslationsContext);
|
|
283
|
-
if (value === undefined) {
|
|
284
|
-
throw new Error("useTranslations must be used within the TranslationsContext");
|
|
285
|
-
}
|
|
286
|
-
const translate = useCallback2((key, variables) => {
|
|
287
|
-
const translation = value.translations[key];
|
|
288
|
-
if (!translation) {
|
|
289
|
-
console.warn(`[@bgord/ui] missing translation for key: ${key}`);
|
|
290
|
-
return key;
|
|
291
|
-
}
|
|
292
|
-
if (!variables)
|
|
293
|
-
return translation;
|
|
294
|
-
return Object.entries(variables).reduce((result, [placeholder, value2]) => {
|
|
295
|
-
const regex = new RegExp(`{{${placeholder}}}`, "g");
|
|
296
|
-
return result.replace(regex, String(value2));
|
|
297
|
-
}, translation);
|
|
298
|
-
}, [value.translations]);
|
|
299
|
-
return translate;
|
|
300
|
-
}
|
|
301
|
-
function useLanguage() {
|
|
302
|
-
const value = use(TranslationsContext);
|
|
303
|
-
if (value === undefined) {
|
|
304
|
-
throw new Error("useLanguage must be used within the TranslationsContext");
|
|
305
|
-
}
|
|
306
|
-
return value.language;
|
|
307
|
-
}
|
|
308
|
-
function usePluralize() {
|
|
309
|
-
const language = useLanguage();
|
|
310
|
-
return (options) => pluralize({ ...options, language });
|
|
311
|
-
}
|
|
312
417
|
// src/services/weak-etag.ts
|
|
313
418
|
class WeakETag {
|
|
314
419
|
static fromRevision(revision) {
|
|
@@ -319,13 +424,20 @@ export {
|
|
|
319
424
|
useTranslations,
|
|
320
425
|
useToggle,
|
|
321
426
|
usePluralize,
|
|
427
|
+
useMetaEnterSubmit,
|
|
428
|
+
useLanguageSelector,
|
|
322
429
|
useLanguage,
|
|
430
|
+
useKeyboardShortcuts,
|
|
323
431
|
useHover,
|
|
432
|
+
useFocusKeyboardShortcut,
|
|
324
433
|
useFieldStrategyEnum,
|
|
325
434
|
useField,
|
|
326
435
|
useExitAction,
|
|
436
|
+
useClientFilter,
|
|
327
437
|
pluralize,
|
|
438
|
+
getSafeWindow,
|
|
328
439
|
extractUseToggle,
|
|
440
|
+
exec,
|
|
329
441
|
WeakETag,
|
|
330
442
|
TranslationsContext,
|
|
331
443
|
Rhythm,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function getSafeWindow(): (Window & typeof globalThis) | undefined;
|
package/dist/services/index.d.ts
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
export * from "./colorful";
|
|
2
2
|
export * from "./etag";
|
|
3
|
+
export * from "./exec";
|
|
3
4
|
export * from "./field";
|
|
4
5
|
export * from "./fields";
|
|
5
6
|
export * from "./form";
|
|
7
|
+
export * from "./get-safe-window";
|
|
6
8
|
export * from "./pluralize";
|
|
7
9
|
export * from "./rhythm";
|
|
8
10
|
export * from "./translations";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bgord/ui",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"exports": {
|
|
6
6
|
".": {
|
|
@@ -25,17 +25,18 @@
|
|
|
25
25
|
"access": "public"
|
|
26
26
|
},
|
|
27
27
|
"devDependencies": {
|
|
28
|
+
"@biomejs/biome": "2.0.6",
|
|
29
|
+
"@commitlint/cli": "19.8.1",
|
|
30
|
+
"@commitlint/config-conventional": "19.8.1",
|
|
28
31
|
"@happy-dom/global-registrator": "18.0.1",
|
|
29
32
|
"@testing-library/dom": "10.4.0",
|
|
30
33
|
"@testing-library/jest-dom": "6.6.3",
|
|
31
34
|
"@testing-library/react": "16.3.0",
|
|
32
35
|
"@testing-library/user-event": "14.6.1",
|
|
33
36
|
"@types/bun": "1.2.18",
|
|
37
|
+
"@types/js-cookie": "^3.0.6",
|
|
34
38
|
"@types/react": "19.1.8",
|
|
35
39
|
"@types/react-dom": "19.1.6",
|
|
36
|
-
"@biomejs/biome": "2.0.6",
|
|
37
|
-
"@commitlint/cli": "19.8.1",
|
|
38
|
-
"@commitlint/config-conventional": "19.8.1",
|
|
39
40
|
"cspell": "9.1.5",
|
|
40
41
|
"knip": "5.61.3",
|
|
41
42
|
"lefthook": "1.12.2",
|
|
@@ -43,6 +44,8 @@
|
|
|
43
44
|
"shellcheck": "3.1.0"
|
|
44
45
|
},
|
|
45
46
|
"dependencies": {
|
|
46
|
-
"
|
|
47
|
+
"js-cookie": "3.0.5",
|
|
48
|
+
"polish-plurals": "1.1.0",
|
|
49
|
+
"tinykeys": "3.0.0"
|
|
47
50
|
}
|
|
48
51
|
}
|
package/readme.md
CHANGED
|
@@ -27,16 +27,23 @@ src/
|
|
|
27
27
|
├── components
|
|
28
28
|
│ ├── button.tsx
|
|
29
29
|
├── hooks
|
|
30
|
+
│ ├── use-client-filter.ts
|
|
30
31
|
│ ├── use-exit-action.ts
|
|
31
32
|
│ ├── use-field.ts
|
|
33
|
+
│ ├── use-focus-shortcut.ts
|
|
32
34
|
│ ├── use-hover.ts
|
|
35
|
+
│ ├── use-language-selector.tsx
|
|
36
|
+
│ ├── use-meta-enter-submit.tsx
|
|
37
|
+
│ ├── use-shortcuts.ts
|
|
33
38
|
│ └── use-toggle.ts
|
|
34
39
|
└── services
|
|
35
40
|
├── colorful.ts
|
|
36
41
|
├── etag.ts
|
|
42
|
+
├── exec.ts
|
|
37
43
|
├── field.ts
|
|
38
44
|
├── fields.ts
|
|
39
45
|
├── form.ts
|
|
46
|
+
├── get-safe-window.ts
|
|
40
47
|
├── pluralize.ts
|
|
41
48
|
├── rhythm.ts
|
|
42
49
|
├── translations.tsx
|