@jbpark/use-hooks 2.5.0 → 2.7.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.
package/README.ko.md CHANGED
@@ -84,6 +84,7 @@ function MyComponent() {
84
84
  | `useThrottle` | 값 업데이트를 일정 간격으로 제한 |
85
85
  | `useHistoryState` | 히스토리 개수 제한을 설정할 수 있는 실행 취소/다시 실행 상태 관리 |
86
86
  | `useIntersectionObserver` | 콜백 ref로 요소의 뷰포트 교차 여부 추적 |
87
+ | `useMultiSelect` | shift-클릭 범위 선택을 지원하는 체크박스 스타일 다중 선택 |
87
88
 
88
89
  ## 개발
89
90
 
@@ -118,6 +119,7 @@ src/
118
119
  │ ├── use-image.ts
119
120
  │ ├── use-intersection-observer.ts
120
121
  │ ├── use-local-storage.ts
122
+ │ ├── use-multi-select.ts
121
123
  │ ├── use-recursive-timeout.ts
122
124
  │ ├── use-responsive-size.ts
123
125
  │ ├── use-scroll-to-elements.ts
package/README.md CHANGED
@@ -84,6 +84,7 @@ function MyComponent() {
84
84
  | `useThrottle` | Throttle value updates to a fixed interval |
85
85
  | `useHistoryState` | Undo/redo state management with a configurable history limit |
86
86
  | `useIntersectionObserver` | Track an element's viewport intersection via a callback ref |
87
+ | `useMultiSelect` | Checkbox-style multi-select for a list, with shift-click range selection |
87
88
 
88
89
  ## Development
89
90
 
@@ -118,6 +119,7 @@ src/
118
119
  │ ├── use-image.ts
119
120
  │ ├── use-intersection-observer.ts
120
121
  │ ├── use-local-storage.ts
122
+ │ ├── use-multi-select.ts
121
123
  │ ├── use-recursive-timeout.ts
122
124
  │ ├── use-responsive-size.ts
123
125
  │ ├── use-scroll-to-elements.ts
@@ -3,11 +3,13 @@ import { useBodyScrollLock } from "./use-body-scroll-lock.mjs";
3
3
  import { useClickOutside } from "./use-click-outside.mjs";
4
4
  import { useElementPosition } from "./use-element-position.mjs";
5
5
  import { useElementScroll } from "./use-element-scroll.mjs";
6
+ import { useFileToDataUrl } from "./use-file-to-data-url.mjs";
6
7
  import { useHistoryState } from "./use-history-state.mjs";
7
8
  import { useIntersectionObserver } from "./use-intersection-observer.mjs";
8
9
  import { useResponsiveSize } from "./use-responsive-size.mjs";
9
10
  import { useImage } from "./use-image.mjs";
10
11
  import { useLocalStorage } from "./use-local-storage.mjs";
12
+ import { useMultiSelect } from "./use-multi-select.mjs";
11
13
  import { useRecursiveTimeout } from "./use-recursive-timeout.mjs";
12
14
  import { useScrollToElements } from "./use-scroll-to-elements.mjs";
13
15
  import { useThrottle } from "./use-throttle.mjs";
@@ -3,11 +3,13 @@ import useBodyScrollLock from "./use-body-scroll-lock.mjs";
3
3
  import useClickOutside from "./use-click-outside.mjs";
4
4
  import useElementPosition from "./use-element-position.mjs";
5
5
  import useElementScroll from "./use-element-scroll.mjs";
6
+ import useFileToDataUrl from "./use-file-to-data-url.mjs";
6
7
  import useHistoryState from "./use-history-state.mjs";
7
8
  import useIntersectionObserver from "./use-intersection-observer.mjs";
8
9
  import useResponsiveSize from "./use-responsive-size.mjs";
9
10
  import useImage from "./use-image.mjs";
10
11
  import useLocalStorage from "./use-local-storage.mjs";
12
+ import useMultiSelect from "./use-multi-select.mjs";
11
13
  import useRecursiveTimeout from "./use-recursive-timeout.mjs";
12
14
  import useScrollToElements from "./use-scroll-to-elements.mjs";
13
15
  import useThrottle from "./use-throttle.mjs";
@@ -0,0 +1,5 @@
1
+ //#region src/hooks/use-file-to-data-url.d.ts
2
+ declare const useFileToDataUrl: () => (file: File) => Promise<string>;
3
+ //#endregion
4
+ export { useFileToDataUrl };
5
+ //# sourceMappingURL=use-file-to-data-url.d.mts.map
@@ -0,0 +1,22 @@
1
+ import { useCallback } from "react";
2
+
3
+ //#region src/hooks/use-file-to-data-url.ts
4
+ const useFileToDataUrl = () => {
5
+ return useCallback((file) => {
6
+ return new Promise((resolve, reject) => {
7
+ const reader = new FileReader();
8
+ reader.onload = () => {
9
+ if (typeof reader.result === "string") resolve(reader.result);
10
+ else reject(/* @__PURE__ */ new Error("Failed to read file as a data URL."));
11
+ };
12
+ reader.onerror = () => {
13
+ reject(reader.error ?? /* @__PURE__ */ new Error("Failed to read file."));
14
+ };
15
+ reader.readAsDataURL(file);
16
+ });
17
+ }, []);
18
+ };
19
+
20
+ //#endregion
21
+ export { useFileToDataUrl as default };
22
+ //# sourceMappingURL=use-file-to-data-url.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-file-to-data-url.mjs","names":[],"sources":["../../src/hooks/use-file-to-data-url.ts"],"sourcesContent":["import { useCallback } from 'react';\n\nconst useFileToDataUrl = () => {\n const readAsDataUrl = useCallback((file: File): Promise<string> => {\n return new Promise((resolve, reject) => {\n const reader = new FileReader();\n\n reader.onload = () => {\n if (typeof reader.result === 'string') {\n resolve(reader.result);\n } else {\n reject(new Error('Failed to read file as a data URL.'));\n }\n };\n\n reader.onerror = () => {\n reject(reader.error ?? new Error('Failed to read file.'));\n };\n\n reader.readAsDataURL(file);\n });\n }, []);\n\n return readAsDataUrl;\n};\n\nexport default useFileToDataUrl;\n"],"mappings":";;;AAEA,MAAM,yBAAyB;AAqB7B,QApBsB,aAAa,SAAgC;AACjE,SAAO,IAAI,SAAS,SAAS,WAAW;GACtC,MAAM,SAAS,IAAI,YAAY;AAE/B,UAAO,eAAe;AACpB,QAAI,OAAO,OAAO,WAAW,SAC3B,SAAQ,OAAO,OAAO;QAEtB,wBAAO,IAAI,MAAM,qCAAqC,CAAC;;AAI3D,UAAO,gBAAgB;AACrB,WAAO,OAAO,yBAAS,IAAI,MAAM,uBAAuB,CAAC;;AAG3D,UAAO,cAAc,KAAK;IAC1B;IACD,EAAE,CAAC"}
@@ -0,0 +1,11 @@
1
+ //#region src/hooks/use-multi-select.d.ts
2
+ declare const useMultiSelect: (count: number) => {
3
+ readonly selected: Set<number>;
4
+ readonly isSelected: (index: number) => boolean;
5
+ readonly toggle: (index: number, shiftKey?: boolean) => void;
6
+ readonly clear: () => void;
7
+ readonly replace: (indices: Set<number>) => void;
8
+ };
9
+ //#endregion
10
+ export { useMultiSelect };
11
+ //# sourceMappingURL=use-multi-select.d.mts.map
@@ -0,0 +1,43 @@
1
+ import { useCallback, useMemo, useState } from "react";
2
+
3
+ //#region src/hooks/use-multi-select.ts
4
+ const useMultiSelect = (count) => {
5
+ const [rawSelected, setRawSelected] = useState(/* @__PURE__ */ new Set());
6
+ const [anchor, setAnchor] = useState(null);
7
+ const selected = useMemo(() => {
8
+ const next = new Set([...rawSelected].filter((index) => index < count));
9
+ return next.size === rawSelected.size ? rawSelected : next;
10
+ }, [rawSelected, count]);
11
+ const toggle = useCallback((index, shiftKey = false) => {
12
+ setRawSelected((prev) => {
13
+ const next = new Set(prev);
14
+ if (shiftKey && anchor !== null) {
15
+ const [start, end] = anchor < index ? [anchor, index] : [index, anchor];
16
+ for (let i = start; i <= end; i++) next.add(i);
17
+ return next;
18
+ }
19
+ if (next.has(index)) next.delete(index);
20
+ else next.add(index);
21
+ return next;
22
+ });
23
+ setAnchor(index);
24
+ }, [anchor]);
25
+ const clear = useCallback(() => {
26
+ setRawSelected(/* @__PURE__ */ new Set());
27
+ setAnchor(null);
28
+ }, []);
29
+ const replace = useCallback((indices) => {
30
+ setRawSelected(indices);
31
+ }, []);
32
+ return {
33
+ selected,
34
+ isSelected: useCallback((index) => selected.has(index), [selected]),
35
+ toggle,
36
+ clear,
37
+ replace
38
+ };
39
+ };
40
+
41
+ //#endregion
42
+ export { useMultiSelect as default };
43
+ //# sourceMappingURL=use-multi-select.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-multi-select.mjs","names":[],"sources":["../../src/hooks/use-multi-select.ts"],"sourcesContent":["import { useCallback, useMemo, useState } from 'react';\n\nconst useMultiSelect = (count: number) => {\n const [rawSelected, setRawSelected] = useState<Set<number>>(new Set());\n const [anchor, setAnchor] = useState<number | null>(null);\n\n // Indices can outlive the item they pointed to (e.g. after the backing\n // list shrinks) — clamp against the current count on every render\n // instead of syncing state back via an effect.\n const selected = useMemo(() => {\n const next = new Set([...rawSelected].filter(index => index < count));\n return next.size === rawSelected.size ? rawSelected : next;\n }, [rawSelected, count]);\n\n const toggle = useCallback(\n (index: number, shiftKey = false) => {\n setRawSelected(prev => {\n const next = new Set(prev);\n\n if (shiftKey && anchor !== null) {\n const [start, end] =\n anchor < index ? [anchor, index] : [index, anchor];\n\n for (let i = start; i <= end; i++) {\n next.add(i);\n }\n return next;\n }\n\n if (next.has(index)) {\n next.delete(index);\n } else {\n next.add(index);\n }\n return next;\n });\n setAnchor(index);\n },\n [anchor],\n );\n\n const clear = useCallback(() => {\n setRawSelected(new Set());\n setAnchor(null);\n }, []);\n\n const replace = useCallback((indices: Set<number>) => {\n setRawSelected(indices);\n }, []);\n\n const isSelected = useCallback(\n (index: number) => selected.has(index),\n [selected],\n );\n\n return {\n selected,\n isSelected,\n toggle,\n clear,\n replace,\n } as const;\n};\n\nexport default useMultiSelect;\n"],"mappings":";;;AAEA,MAAM,kBAAkB,UAAkB;CACxC,MAAM,CAAC,aAAa,kBAAkB,yBAAsB,IAAI,KAAK,CAAC;CACtE,MAAM,CAAC,QAAQ,aAAa,SAAwB,KAAK;CAKzD,MAAM,WAAW,cAAc;EAC7B,MAAM,OAAO,IAAI,IAAI,CAAC,GAAG,YAAY,CAAC,QAAO,UAAS,QAAQ,MAAM,CAAC;AACrE,SAAO,KAAK,SAAS,YAAY,OAAO,cAAc;IACrD,CAAC,aAAa,MAAM,CAAC;CAExB,MAAM,SAAS,aACZ,OAAe,WAAW,UAAU;AACnC,kBAAe,SAAQ;GACrB,MAAM,OAAO,IAAI,IAAI,KAAK;AAE1B,OAAI,YAAY,WAAW,MAAM;IAC/B,MAAM,CAAC,OAAO,OACZ,SAAS,QAAQ,CAAC,QAAQ,MAAM,GAAG,CAAC,OAAO,OAAO;AAEpD,SAAK,IAAI,IAAI,OAAO,KAAK,KAAK,IAC5B,MAAK,IAAI,EAAE;AAEb,WAAO;;AAGT,OAAI,KAAK,IAAI,MAAM,CACjB,MAAK,OAAO,MAAM;OAElB,MAAK,IAAI,MAAM;AAEjB,UAAO;IACP;AACF,YAAU,MAAM;IAElB,CAAC,OAAO,CACT;CAED,MAAM,QAAQ,kBAAkB;AAC9B,iCAAe,IAAI,KAAK,CAAC;AACzB,YAAU,KAAK;IACd,EAAE,CAAC;CAEN,MAAM,UAAU,aAAa,YAAyB;AACpD,iBAAe,QAAQ;IACtB,EAAE,CAAC;AAON,QAAO;EACL;EACA,YAPiB,aAChB,UAAkB,SAAS,IAAI,MAAM,EACtC,CAAC,SAAS,CACX;EAKC;EACA;EACA;EACD"}
package/dist/index.d.mts CHANGED
@@ -3,11 +3,13 @@ import { useBodyScrollLock } from "./hooks/use-body-scroll-lock.mjs";
3
3
  import { useClickOutside } from "./hooks/use-click-outside.mjs";
4
4
  import { useElementPosition } from "./hooks/use-element-position.mjs";
5
5
  import { useElementScroll } from "./hooks/use-element-scroll.mjs";
6
+ import { useFileToDataUrl } from "./hooks/use-file-to-data-url.mjs";
6
7
  import { useHistoryState } from "./hooks/use-history-state.mjs";
7
8
  import { useIntersectionObserver } from "./hooks/use-intersection-observer.mjs";
8
9
  import { useResponsiveSize } from "./hooks/use-responsive-size.mjs";
9
10
  import { useImage } from "./hooks/use-image.mjs";
10
11
  import { useLocalStorage } from "./hooks/use-local-storage.mjs";
12
+ import { useMultiSelect } from "./hooks/use-multi-select.mjs";
11
13
  import { useRecursiveTimeout } from "./hooks/use-recursive-timeout.mjs";
12
14
  import { useScrollToElements } from "./hooks/use-scroll-to-elements.mjs";
13
15
  import { useThrottle } from "./hooks/use-throttle.mjs";
@@ -15,4 +17,4 @@ import { useTimeline } from "./hooks/use-timeline.mjs";
15
17
  import { useWindowScroll } from "./hooks/use-window-scroll.mjs";
16
18
  import { useViewport } from "./hooks/use-viewport.mjs";
17
19
  import "./hooks/index.mjs";
18
- export { useBodyScrollLock, useClickOutside, useDebounce, useElementPosition, useElementScroll, useHistoryState, useImage, useIntersectionObserver, useLocalStorage, useRecursiveTimeout, useResponsiveSize, useScrollToElements, useThrottle, useTimeline, useViewport, useWindowScroll };
20
+ export { useBodyScrollLock, useClickOutside, useDebounce, useElementPosition, useElementScroll, useFileToDataUrl, useHistoryState, useImage, useIntersectionObserver, useLocalStorage, useMultiSelect, useRecursiveTimeout, useResponsiveSize, useScrollToElements, useThrottle, useTimeline, useViewport, useWindowScroll };
package/dist/index.mjs CHANGED
@@ -3,11 +3,13 @@ import useBodyScrollLock from "./hooks/use-body-scroll-lock.mjs";
3
3
  import useClickOutside from "./hooks/use-click-outside.mjs";
4
4
  import useElementPosition from "./hooks/use-element-position.mjs";
5
5
  import useElementScroll from "./hooks/use-element-scroll.mjs";
6
+ import useFileToDataUrl from "./hooks/use-file-to-data-url.mjs";
6
7
  import useHistoryState from "./hooks/use-history-state.mjs";
7
8
  import useIntersectionObserver from "./hooks/use-intersection-observer.mjs";
8
9
  import useResponsiveSize from "./hooks/use-responsive-size.mjs";
9
10
  import useImage from "./hooks/use-image.mjs";
10
11
  import useLocalStorage from "./hooks/use-local-storage.mjs";
12
+ import useMultiSelect from "./hooks/use-multi-select.mjs";
11
13
  import useRecursiveTimeout from "./hooks/use-recursive-timeout.mjs";
12
14
  import useScrollToElements from "./hooks/use-scroll-to-elements.mjs";
13
15
  import useThrottle from "./hooks/use-throttle.mjs";
@@ -16,4 +18,4 @@ import useWindowScroll from "./hooks/use-window-scroll.mjs";
16
18
  import useViewport from "./hooks/use-viewport.mjs";
17
19
  import "./hooks/index.mjs";
18
20
 
19
- export { useBodyScrollLock, useClickOutside, useDebounce, useElementPosition, useElementScroll, useHistoryState, useImage, useIntersectionObserver, useLocalStorage, useRecursiveTimeout, useResponsiveSize, useScrollToElements, useThrottle, useTimeline, useViewport, useWindowScroll };
21
+ export { useBodyScrollLock, useClickOutside, useDebounce, useElementPosition, useElementScroll, useFileToDataUrl, useHistoryState, useImage, useIntersectionObserver, useLocalStorage, useMultiSelect, useRecursiveTimeout, useResponsiveSize, useScrollToElements, useThrottle, useTimeline, useViewport, useWindowScroll };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jbpark/use-hooks",
3
- "version": "2.5.0",
3
+ "version": "2.7.0",
4
4
  "description": "A collection of reusable React 19 hooks for common UI and interaction patterns",
5
5
  "keywords": [
6
6
  "react",
@@ -54,7 +54,7 @@
54
54
  "devDependencies": {
55
55
  "@changesets/cli": "^2.31.1",
56
56
  "@eslint/js": "^9.33.0",
57
- "@jbpark/ui-kit": "^2.7.0",
57
+ "@jbpark/ui-kit": "^3.0.0",
58
58
  "@radix-ui/react-icons": "^1.3.2",
59
59
  "@trivago/prettier-plugin-sort-imports": "^5.2.2",
60
60
  "@types/node": "^24.5.2",