@tutti-os/ui-react-hooks 0.0.132 → 0.0.134
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.md +3 -0
- package/dist/index.js +9 -4
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -10,6 +10,9 @@ hooks and UI components in their owning packages.
|
|
|
10
10
|
|
|
11
11
|
Prefer this package over adding new direct `useSyncExternalStore` wrappers in
|
|
12
12
|
shared frontend packages so subscription semantics stay consistent.
|
|
13
|
+
`useExternalStoreSnapshot` keeps its React subscription callbacks stable while
|
|
14
|
+
the source identity is unchanged, so source consumers do not unsubscribe and
|
|
15
|
+
resubscribe on each render.
|
|
13
16
|
|
|
14
17
|
This package does not replace adapter-level snapshot memoization. If a non-React
|
|
15
18
|
adapter exposes a derived `getSnapshot()`, keep reference-stable snapshot reuse
|
package/dist/index.js
CHANGED
|
@@ -113,13 +113,18 @@ function useExternalStoreSelector(source, selector) {
|
|
|
113
113
|
}
|
|
114
114
|
|
|
115
115
|
// src/useExternalStoreSnapshot.ts
|
|
116
|
-
import { useSyncExternalStore as useSyncExternalStore2 } from "react";
|
|
116
|
+
import { useCallback, useSyncExternalStore as useSyncExternalStore2 } from "react";
|
|
117
117
|
function useExternalStoreSnapshot(source) {
|
|
118
|
-
|
|
118
|
+
const subscribe = useCallback(
|
|
119
119
|
(listener) => source.subscribe(listener),
|
|
120
|
-
|
|
121
|
-
() => (source.getServerSnapshot ?? source.getSnapshot)()
|
|
120
|
+
[source]
|
|
122
121
|
);
|
|
122
|
+
const getSnapshot = useCallback(() => source.getSnapshot(), [source]);
|
|
123
|
+
const getServerSnapshot = useCallback(
|
|
124
|
+
() => (source.getServerSnapshot ?? source.getSnapshot)(),
|
|
125
|
+
[source]
|
|
126
|
+
);
|
|
127
|
+
return useSyncExternalStore2(subscribe, getSnapshot, getServerSnapshot);
|
|
123
128
|
}
|
|
124
129
|
export {
|
|
125
130
|
resolveComposedInputValueSync,
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/useComposedInputValue.ts","../src/useExternalStoreSelector.ts","../src/useExternalStoreSnapshot.ts"],"sourcesContent":["import {\n useEffect,\n useRef,\n useState,\n type ChangeEvent,\n type CompositionEvent,\n type FocusEvent\n} from \"react\";\n\nexport interface ComposedInputPendingCommit {\n committedValue: string;\n previousValue: string;\n}\n\nexport interface ComposedInputValueSyncInput {\n isComposing: boolean;\n localValue: string;\n pendingCommit: ComposedInputPendingCommit | null;\n value: string;\n}\n\nexport interface ComposedInputValueSyncResult {\n pendingCommit: ComposedInputPendingCommit | null;\n shouldSyncLocalValue: boolean;\n value: string;\n}\n\nexport interface UseComposedInputValueInput {\n onCommit: (value: string) => void;\n value: string;\n}\n\nexport interface UseComposedInputValueResult {\n clearValue: () => void;\n commitValue: (value: string) => void;\n isComposing: boolean;\n onBlur: (event: FocusEvent<HTMLInputElement>) => void;\n onChange: (event: ChangeEvent<HTMLInputElement>) => void;\n onCompositionEnd: (event: CompositionEvent<HTMLInputElement>) => void;\n onCompositionStart: () => void;\n value: string;\n}\n\nexport function resolveComposedInputValueSync(\n input: ComposedInputValueSyncInput\n): ComposedInputValueSyncResult {\n if (input.isComposing) {\n return {\n pendingCommit: input.pendingCommit,\n shouldSyncLocalValue: false,\n value: input.localValue\n };\n }\n\n if (input.pendingCommit !== null) {\n if (input.value === input.pendingCommit.committedValue) {\n return {\n pendingCommit: null,\n shouldSyncLocalValue: input.localValue !== input.value,\n value: input.value\n };\n }\n\n if (\n input.localValue === input.pendingCommit.committedValue &&\n input.value === input.pendingCommit.previousValue\n ) {\n return {\n pendingCommit: input.pendingCommit,\n shouldSyncLocalValue: false,\n value: input.localValue\n };\n }\n }\n\n return {\n pendingCommit: null,\n shouldSyncLocalValue: input.localValue !== input.value,\n value: input.value\n };\n}\n\nexport function useComposedInputValue({\n onCommit,\n value\n}: UseComposedInputValueInput): UseComposedInputValueResult {\n const pendingCommitRef = useRef<ComposedInputPendingCommit | null>(null);\n const isComposingRef = useRef(false);\n const [localValue, setLocalValue] = useState(value);\n const [isComposing, setIsComposing] = useState(false);\n\n useEffect(() => {\n const syncResult = resolveComposedInputValueSync({\n isComposing,\n localValue,\n pendingCommit: pendingCommitRef.current,\n value\n });\n\n pendingCommitRef.current = syncResult.pendingCommit;\n if (syncResult.shouldSyncLocalValue) {\n setLocalValue(syncResult.value);\n }\n }, [isComposing, localValue, value]);\n\n const commitValue = (nextValue: string) => {\n pendingCommitRef.current = {\n committedValue: nextValue,\n previousValue: value\n };\n setLocalValue(nextValue);\n onCommit(nextValue);\n };\n\n return {\n clearValue: () => {\n commitValue(\"\");\n },\n commitValue,\n isComposing,\n onBlur: (event: FocusEvent<HTMLInputElement>) => {\n commitValue(event.currentTarget.value);\n },\n onChange: (event: ChangeEvent<HTMLInputElement>) => {\n const nextValue = event.currentTarget.value;\n if (!isComposingRef.current) {\n commitValue(nextValue);\n return;\n }\n setLocalValue(nextValue);\n },\n onCompositionEnd: (event: CompositionEvent<HTMLInputElement>) => {\n isComposingRef.current = false;\n setIsComposing(false);\n commitValue(event.currentTarget.value);\n },\n onCompositionStart: () => {\n isComposingRef.current = true;\n setIsComposing(true);\n },\n value: localValue\n };\n}\n","import { useRef, useSyncExternalStore } from \"react\";\nimport type { ExternalStoreSnapshotSource } from \"./types.ts\";\n\nexport function useExternalStoreSelector<TSnapshot, TResult>(\n source: ExternalStoreSnapshotSource<TSnapshot>,\n selector: (snapshot: TSnapshot) => TResult\n): TResult {\n const selectedSnapshotRef = useRef<{\n selector: (snapshot: TSnapshot) => TResult;\n snapshot: TSnapshot;\n value: TResult;\n } | null>(null);\n\n const readSelection = (snapshot: TSnapshot) => {\n const selectedSnapshot = selectedSnapshotRef.current;\n if (\n selectedSnapshot?.snapshot === snapshot &&\n selectedSnapshot.selector === selector\n ) {\n return selectedSnapshot.value;\n }\n\n const value = selector(snapshot);\n selectedSnapshotRef.current = { selector, snapshot, value };\n return value;\n };\n\n return useSyncExternalStore(\n (listener) => source.subscribe(listener),\n () => readSelection(source.getSnapshot()),\n () => readSelection((source.getServerSnapshot ?? source.getSnapshot)())\n );\n}\n","import { useSyncExternalStore } from \"react\";\nimport type { ExternalStoreSnapshotSource } from \"./types.ts\";\n\nexport function useExternalStoreSnapshot<TSnapshot>(\n source: ExternalStoreSnapshotSource<TSnapshot>\n): TSnapshot {\n
|
|
1
|
+
{"version":3,"sources":["../src/useComposedInputValue.ts","../src/useExternalStoreSelector.ts","../src/useExternalStoreSnapshot.ts"],"sourcesContent":["import {\n useEffect,\n useRef,\n useState,\n type ChangeEvent,\n type CompositionEvent,\n type FocusEvent\n} from \"react\";\n\nexport interface ComposedInputPendingCommit {\n committedValue: string;\n previousValue: string;\n}\n\nexport interface ComposedInputValueSyncInput {\n isComposing: boolean;\n localValue: string;\n pendingCommit: ComposedInputPendingCommit | null;\n value: string;\n}\n\nexport interface ComposedInputValueSyncResult {\n pendingCommit: ComposedInputPendingCommit | null;\n shouldSyncLocalValue: boolean;\n value: string;\n}\n\nexport interface UseComposedInputValueInput {\n onCommit: (value: string) => void;\n value: string;\n}\n\nexport interface UseComposedInputValueResult {\n clearValue: () => void;\n commitValue: (value: string) => void;\n isComposing: boolean;\n onBlur: (event: FocusEvent<HTMLInputElement>) => void;\n onChange: (event: ChangeEvent<HTMLInputElement>) => void;\n onCompositionEnd: (event: CompositionEvent<HTMLInputElement>) => void;\n onCompositionStart: () => void;\n value: string;\n}\n\nexport function resolveComposedInputValueSync(\n input: ComposedInputValueSyncInput\n): ComposedInputValueSyncResult {\n if (input.isComposing) {\n return {\n pendingCommit: input.pendingCommit,\n shouldSyncLocalValue: false,\n value: input.localValue\n };\n }\n\n if (input.pendingCommit !== null) {\n if (input.value === input.pendingCommit.committedValue) {\n return {\n pendingCommit: null,\n shouldSyncLocalValue: input.localValue !== input.value,\n value: input.value\n };\n }\n\n if (\n input.localValue === input.pendingCommit.committedValue &&\n input.value === input.pendingCommit.previousValue\n ) {\n return {\n pendingCommit: input.pendingCommit,\n shouldSyncLocalValue: false,\n value: input.localValue\n };\n }\n }\n\n return {\n pendingCommit: null,\n shouldSyncLocalValue: input.localValue !== input.value,\n value: input.value\n };\n}\n\nexport function useComposedInputValue({\n onCommit,\n value\n}: UseComposedInputValueInput): UseComposedInputValueResult {\n const pendingCommitRef = useRef<ComposedInputPendingCommit | null>(null);\n const isComposingRef = useRef(false);\n const [localValue, setLocalValue] = useState(value);\n const [isComposing, setIsComposing] = useState(false);\n\n useEffect(() => {\n const syncResult = resolveComposedInputValueSync({\n isComposing,\n localValue,\n pendingCommit: pendingCommitRef.current,\n value\n });\n\n pendingCommitRef.current = syncResult.pendingCommit;\n if (syncResult.shouldSyncLocalValue) {\n setLocalValue(syncResult.value);\n }\n }, [isComposing, localValue, value]);\n\n const commitValue = (nextValue: string) => {\n pendingCommitRef.current = {\n committedValue: nextValue,\n previousValue: value\n };\n setLocalValue(nextValue);\n onCommit(nextValue);\n };\n\n return {\n clearValue: () => {\n commitValue(\"\");\n },\n commitValue,\n isComposing,\n onBlur: (event: FocusEvent<HTMLInputElement>) => {\n commitValue(event.currentTarget.value);\n },\n onChange: (event: ChangeEvent<HTMLInputElement>) => {\n const nextValue = event.currentTarget.value;\n if (!isComposingRef.current) {\n commitValue(nextValue);\n return;\n }\n setLocalValue(nextValue);\n },\n onCompositionEnd: (event: CompositionEvent<HTMLInputElement>) => {\n isComposingRef.current = false;\n setIsComposing(false);\n commitValue(event.currentTarget.value);\n },\n onCompositionStart: () => {\n isComposingRef.current = true;\n setIsComposing(true);\n },\n value: localValue\n };\n}\n","import { useRef, useSyncExternalStore } from \"react\";\nimport type { ExternalStoreSnapshotSource } from \"./types.ts\";\n\nexport function useExternalStoreSelector<TSnapshot, TResult>(\n source: ExternalStoreSnapshotSource<TSnapshot>,\n selector: (snapshot: TSnapshot) => TResult\n): TResult {\n const selectedSnapshotRef = useRef<{\n selector: (snapshot: TSnapshot) => TResult;\n snapshot: TSnapshot;\n value: TResult;\n } | null>(null);\n\n const readSelection = (snapshot: TSnapshot) => {\n const selectedSnapshot = selectedSnapshotRef.current;\n if (\n selectedSnapshot?.snapshot === snapshot &&\n selectedSnapshot.selector === selector\n ) {\n return selectedSnapshot.value;\n }\n\n const value = selector(snapshot);\n selectedSnapshotRef.current = { selector, snapshot, value };\n return value;\n };\n\n return useSyncExternalStore(\n (listener) => source.subscribe(listener),\n () => readSelection(source.getSnapshot()),\n () => readSelection((source.getServerSnapshot ?? source.getSnapshot)())\n );\n}\n","import { useCallback, useSyncExternalStore } from \"react\";\nimport type { ExternalStoreSnapshotSource } from \"./types.ts\";\n\nexport function useExternalStoreSnapshot<TSnapshot>(\n source: ExternalStoreSnapshotSource<TSnapshot>\n): TSnapshot {\n const subscribe = useCallback(\n (listener: () => void) => source.subscribe(listener),\n [source]\n );\n const getSnapshot = useCallback(() => source.getSnapshot(), [source]);\n const getServerSnapshot = useCallback(\n () => (source.getServerSnapshot ?? source.getSnapshot)(),\n [source]\n );\n return useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot);\n}\n"],"mappings":"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","names":["useRef","useSyncExternalStore"]}
|