diff --git a/src/component/sugar/create.ts b/src/component/sugar/create.ts index 2f3756a..9349127 100644 --- a/src/component/sugar/create.ts +++ b/src/component/sugar/create.ts @@ -1,9 +1,10 @@ -import type { Sugar, SugarObjectNode, SugarUser, SugarUserReshaper } from '.'; +import type { Sugar, SugarObjectNode, SugarUser, SugarUserReshaper, SugarValue } from '.'; import { SugarDownstreamEventEmitter } from '../../util/events/downstreamEvent'; import { SugarUpstreamEventEmitter } from '../../util/events/upstreamEvent'; import type { SugarObject } from '../../util/object'; import { isSugarObject } from '../../util/object'; import { useSugar } from './use'; +import { useSugarFromRef } from './useFromRef'; export function createEmptySugar(path: string, template: T): Sugar { const sugar: Sugar = { @@ -14,6 +15,9 @@ export function createEmptySugar(path: string, template: T): Sugar { downstream: new SugarDownstreamEventEmitter(), use: (options: SugarUserReshaper) => useSugar(sugar, options), + useFromRef: + (param: { get: () => SugarValue, set: (value: T) => void }) => + useSugarFromRef(sugar, param), }; if (isSugarObject(template)) { diff --git a/src/component/sugar/index.ts b/src/component/sugar/index.ts index 3b12293..b044171 100644 --- a/src/component/sugar/index.ts +++ b/src/component/sugar/index.ts @@ -17,6 +17,9 @@ export type SugarData = { template: T, upstream: SugarUpstreamEventEmitter, downstream: SugarDownstreamEventEmitter, + useFromRef: (param: { get: () => SugarValue, set: (value: T) => void }) => { + onChange: () => void, onBlur: () => void + }, } & ( T extends SugarObject ? { diff --git a/src/component/sugar/useFromRef.ts b/src/component/sugar/useFromRef.ts new file mode 100644 index 0000000..2e1184c --- /dev/null +++ b/src/component/sugar/useFromRef.ts @@ -0,0 +1,33 @@ +import type { Sugar, SugarValue } from '.'; +import { SugarFormError } from '../../util/error'; +import { setDirty } from './dirty'; + +export function useSugarFromRef( + sugar: Sugar, param: { get: () => SugarValue, set: (value: T) => void }, +): { + onChange: () => void, onBlur: () => void +} { + + const refreshDirty = (): void => { + if (!sugar.mounted) throw new SugarFormError('SF0021', `Path: ${sugar.path}}`); + const value = sugar.get(); + setDirty(sugar, !value.success || sugar.template !== value.value); + }; + + const updateSugar = sugar as Sugar & { mounted: true }; + updateSugar.mounted = true; + updateSugar.get = param.get; + updateSugar.set = (v): void => { + param.set(v); + refreshDirty(); + }; + updateSugar.isDirty = false; + + return { + onChange: (): void => { + if (!sugar.mounted) throw new SugarFormError('SF0021', `Path: ${sugar.path}}`); + if (!sugar.isDirty) setDirty(sugar, true); + }, + onBlur: () => refreshDirty(), + }; +} diff --git a/tests/sugarUse.test.ts b/tests/sugarUse.test.ts index 946500c..549ac10 100644 --- a/tests/sugarUse.test.ts +++ b/tests/sugarUse.test.ts @@ -26,6 +26,7 @@ describe('wrapSugar', () => { downstream: expect.any(SugarDownstreamEventEmitter), use: expect.any(Function), useObject: expect.any(Function), + useFromRef: expect.any(Function), }, d: { mounted: false, @@ -34,6 +35,7 @@ describe('wrapSugar', () => { upstream: expect.any(SugarUpstreamEventEmitter), downstream: expect.any(SugarDownstreamEventEmitter), use: expect.any(Function), + useFromRef: expect.any(Function), }, }; @@ -58,6 +60,7 @@ describe('useObject', () => { upstream: expect.any(SugarUpstreamEventEmitter), downstream: expect.any(SugarDownstreamEventEmitter), use: expect.any(Function), + useFromRef: expect.any(Function), }, c: { mounted: false, @@ -66,6 +69,7 @@ describe('useObject', () => { upstream: expect.any(SugarUpstreamEventEmitter), downstream: expect.any(SugarDownstreamEventEmitter), use: expect.any(Function), + useFromRef: expect.any(Function), }, }; diff --git a/tests/useFromRef.test.ts b/tests/useFromRef.test.ts new file mode 100644 index 0000000..5ea4b08 --- /dev/null +++ b/tests/useFromRef.test.ts @@ -0,0 +1,50 @@ +import { describe, it } from '@jest/globals'; +import { renderHook } from '@testing-library/react'; +import type { Sugar, SugarValue } from '../src/component/sugar'; +import { createEmptySugar } from '../src/component/sugar/create'; + + +const safeSet = (a: Sugar, value: T): void => { + if (a.mounted) { + a.get = (): SugarValue => ({ success: true, value }); + a.set(value); + } +}; + +const safeEdit = (a: Sugar, value: T): void => { + if (a.mounted) { + a.get = (): SugarValue => ({ success: true, value }); + } +}; + +describe('useFromRef', () => { + + it('should work', () => { + const a = createEmptySugar('', 'abc'); + const setter = jest.fn(); + + const { result: { current: { onChange, onBlur } } } = renderHook(() => + a.useFromRef({ + get: () => ({ success: true, value: 'abc' }), + set: setter, + }), + ); + + expect(a.mounted).toBe(true); + expect(a.mounted && a.isDirty).toBe(false); + expect(a.mounted && a.get()).toStrictEqual({ success: true, value: 'abc' }); + safeSet(a, 'def'); + expect(a.mounted && a.isDirty).toBe(true); + expect(setter).toHaveBeenCalledTimes(1); + expect(setter).toHaveBeenCalledWith('def'); + safeEdit(a, 'ghi'); + onChange(); + expect(a.mounted && a.isDirty).toBe(true); + expect(a.mounted && a.get()).toStrictEqual({ success: true, value: 'ghi' }); + safeEdit(a, 'abc'); + onBlur(); + expect(a.mounted && a.isDirty).toBe(false); + + }); + +});