From 36746881fc6f29b6fd9e9a79a0482c6854bab5be Mon Sep 17 00:00:00 2001 From: Viktor Pasynok Date: Tue, 4 Nov 2025 17:39:30 +0700 Subject: [PATCH 01/22] changed: insertSorted --- src/init.tsx | 24 +++++++++++------------- 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/src/init.tsx b/src/init.tsx index 1b614b2..e07be7e 100644 --- a/src/init.tsx +++ b/src/init.tsx @@ -10,17 +10,16 @@ type Entries = { const isNil = (x: T | undefined | null): x is undefined | null => x === null || x === undefined; -const insertAtPosition = (list: T[], position: number, element: T) => { - const newList = [...list]; +const insertSorted = (list: T[], element: T) => { + const elementOrder = element.order ?? 0; + const insertIndex = list.findIndex((existing) => (existing.order ?? 0) > elementOrder); - if (position <= 0) { - newList.unshift(element); - } else if (position >= list.length) { - newList.push(element); - } else { - newList.splice(position, 0, element); + if (insertIndex === -1) { + return [...list, element]; } + const newList = [...list]; + newList.splice(insertIndex, 0, element); return newList; }; @@ -67,10 +66,9 @@ const createSlots = >>(config: T) => $slots.on(evt, (state, payload) => { const item = { ...payload, id: nanoid(10) }; - const list = insertAtPosition(state[key], payload.order ?? state[key].length + 1, item); - const sortedList = list.sort((a, b) => (a.order ?? 0) - (b.order ?? 0)); + const list = insertSorted(state[key], item); - return { ...state, [key]: sortedList }; + return { ...state, [key]: list }; }); // @ts-expect-error its ok. avoid extra fn creation @@ -94,9 +92,9 @@ const createSlots = >>(config: T) => const slots = keys.reduce((acc, key) => { const component = memo>((props) => { - const childrens = useStoreMap($slots, (x) => x[key]); + const children = useStoreMap($slots, (x) => x[key]); - return childrens.map((child) => { + return children.map((child) => { if (isNil(child.fn)) { return ( From f0e5a90fa8c856ffc7adca34bac221133559ee9c Mon Sep 17 00:00:00 2001 From: Viktor Pasynok Date: Tue, 4 Nov 2025 17:54:58 +0700 Subject: [PATCH 02/22] refactor: simplify insertSorted function using toSpliced --- src/init.tsx | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/src/init.tsx b/src/init.tsx index e07be7e..de18cc3 100644 --- a/src/init.tsx +++ b/src/init.tsx @@ -14,13 +14,7 @@ const insertSorted = (list: T[], element: T) => { const elementOrder = element.order ?? 0; const insertIndex = list.findIndex((existing) => (existing.order ?? 0) > elementOrder); - if (insertIndex === -1) { - return [...list, element]; - } - - const newList = [...list]; - newList.splice(insertIndex, 0, element); - return newList; + return list.toSpliced(insertIndex === -1 ? list.length : insertIndex, 0, element); }; type CreateSlotIdentifier = () => (_: T) => T; From c5a25b2c6def79785db58e8a7795fc34d9f7f3d2 Mon Sep 17 00:00:00 2001 From: Viktor Pasynok Date: Tue, 4 Nov 2025 19:07:26 +0700 Subject: [PATCH 03/22] refactor: move createSlots and createSlotIdentifier to index.tsx, remove init.tsx --- sandbox.tsx | 15 ++++ src/__tests__/payload.spec-d.tsx | 3 +- src/helpers.tsx | 26 +++++++ src/index.tsx | 97 +++++++++++++++++++++++- src/init.tsx | 122 ------------------------------- tsconfig.json | 2 +- 6 files changed, 140 insertions(+), 125 deletions(-) create mode 100644 sandbox.tsx create mode 100644 src/helpers.tsx delete mode 100644 src/init.tsx diff --git a/sandbox.tsx b/sandbox.tsx new file mode 100644 index 0000000..cdf1ca9 --- /dev/null +++ b/sandbox.tsx @@ -0,0 +1,15 @@ +import { createEvent, sample } from 'effector'; +import { createSlotIdentifier, createSlots } from './src'; + +const { slotsApi } = createSlots({ + ConfirmScreenBottom: createSlotIdentifier<{ id: number }>(), +} as const); + +const evt = createEvent<{ id: number }>(); + +sample({ + clock: evt, + target: slotsApi.insert.into.ConfirmScreenBottom, +}); + +// slotsApi.on(evt).insert.into.ConfirmScreenBottom diff --git a/src/__tests__/payload.spec-d.tsx b/src/__tests__/payload.spec-d.tsx index 8208f03..47dca60 100644 --- a/src/__tests__/payload.spec-d.tsx +++ b/src/__tests__/payload.spec-d.tsx @@ -1,6 +1,7 @@ import React from 'react'; import { expectTypeOf } from 'vitest'; -import { createSlotIdentifier, createSlots, type EmptyObject } from '../init'; +import { EmptyObject } from '../helpers'; +import { createSlotIdentifier, createSlots } from '../init'; const slotId = createSlotIdentifier<{ text: string }>(); const noPropsSlot = createSlotIdentifier(); diff --git a/src/helpers.tsx b/src/helpers.tsx new file mode 100644 index 0000000..bfcc14a --- /dev/null +++ b/src/helpers.tsx @@ -0,0 +1,26 @@ +import React, { useMemo } from 'react'; + +const isNil = (x: T | undefined | null): x is undefined | null => x === null || x === undefined; + +const insertSorted = (list: T[], element: T) => { + const elementOrder = element.order ?? 0; + const insertIndex = list.findIndex((existing) => (existing.order ?? 0) > elementOrder); + + return list.toSpliced(insertIndex === -1 ? list.length : insertIndex, 0, element); +}; + +type EmptyObject = Record; +type Entries = { + [K in keyof T]: [K, T[K]]; +}[keyof T][]; + +// @ts-expect-error its ok +const makeChildWithProps = (child) => + // @ts-expect-error its ok + memo((props) => { + const childProps = useMemo(() => child.fn(props), [props]); + + return ; + }); + +export { insertSorted, isNil, makeChildWithProps, type EmptyObject, type Entries }; diff --git a/src/index.tsx b/src/index.tsx index dda44de..64cfa37 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -1 +1,96 @@ -export { createSlotIdentifier, createSlots, type EmptyObject } from './init'; +import { createEvent, createStore } from 'effector'; +import { useStoreMap } from 'effector-react'; +import { nanoid } from 'nanoid'; +import React, { memo, type FunctionComponent } from 'react'; +import { insertSorted, isNil, makeChildWithProps, type EmptyObject, type Entries } from './helpers'; + +type CreateSlotIdentifier = () => (_: T) => T; + +const createSlotIdentifier: CreateSlotIdentifier = () => (props) => props; + +type SlotFunction = (_: T) => T; + +type Payload = (params: { + component: (props: unknown extends R ? EmptyObject : R extends void ? EmptyObject : R) => React.JSX.Element; + fn?: (arg: T) => R; + order?: number; +}) => void; + +const createSlots = >>(config: T) => { + const entries = Object.entries(config) as Entries; + const keys = entries.map(([k]) => k); + + type SetApi = { + [key in keyof T]: T[key] extends (_: any) => unknown ? Payload[0]> : never; + }; + + type State = { + [key in keyof T]: (Parameters[0] & { id: string })[]; + }; + + type Slots = { + [key in keyof T]: FunctionComponent[0]>; + }; + + type ExtractData

= Parameters[0]; + + const $slots = createStore( + keys.reduce((acc, x) => { + acc[x] = []; + + return acc; + }, {} as State), + ); + + const insertApi = keys.reduce((acc, key) => { + const insert = createEvent>>[0]>(); + + $slots.on(insert, (state, payload) => { + const item = { ...payload, id: nanoid(10) }; + const list = insertSorted(state[key], item); + + return { ...state, [key]: list }; + }); + + // @ts-expect-error its ok. avoid extra fn creation + acc[key] = insert; + + return acc; + }, {} as SetApi); + + const slots = keys.reduce((acc, key) => { + const component = memo>((props) => { + const slotChildren = useStoreMap($slots, (x) => x[key]); + + return slotChildren.map((child) => { + if (isNil(child.fn)) { + return ( + + + + ); + } + + const ChildWithProps = makeChildWithProps(child); + + return ( + + + + ); + }); + }); + + acc[key] = component; + + return acc; + }, {} as Slots); + + const slotsApi = { + insert: { into: insertApi }, + }; + + return { slotsApi, Slots: slots }; +}; + +export { createSlotIdentifier, createSlots }; diff --git a/src/init.tsx b/src/init.tsx deleted file mode 100644 index de18cc3..0000000 --- a/src/init.tsx +++ /dev/null @@ -1,122 +0,0 @@ -import { createEvent, createStore } from 'effector'; -import { useStoreMap } from 'effector-react'; -import { nanoid } from 'nanoid'; -import React, { type FunctionComponent, memo, useMemo } from 'react'; - -type EmptyObject = Record; -type Entries = { - [K in keyof T]: [K, T[K]]; -}[keyof T][]; - -const isNil = (x: T | undefined | null): x is undefined | null => x === null || x === undefined; - -const insertSorted = (list: T[], element: T) => { - const elementOrder = element.order ?? 0; - const insertIndex = list.findIndex((existing) => (existing.order ?? 0) > elementOrder); - - return list.toSpliced(insertIndex === -1 ? list.length : insertIndex, 0, element); -}; - -type CreateSlotIdentifier = () => (_: T) => T; - -const createSlotIdentifier: CreateSlotIdentifier = () => (props) => props; - -type SlotFunction = (_: T) => T; - -type Payload = (params: { - component: (props: unknown extends R ? EmptyObject : R extends void ? EmptyObject : R) => React.JSX.Element; - fn?: (arg: T) => R; - order?: number; -}) => void; - -const createSlots = >>(config: T) => { - const entries = Object.entries(config) as Entries; - const keys = entries.map(([k]) => k); - - type SetApi = { - [key in keyof T]: T[key] extends (_: any) => unknown ? Payload[0]> : never; - }; - - type State = { - [key in keyof T]: (Parameters[0] & { id: string })[]; - }; - - type Slots = { - [key in keyof T]: FunctionComponent[0]>; - }; - - type ExtractData

= Parameters[0]; - - const $slots = createStore( - keys.reduce((acc, x) => { - acc[x] = []; - - return acc; - }, {} as State), - ); - - const insertApi = keys.reduce((acc, key) => { - const evt = createEvent>>[0]>(); - - $slots.on(evt, (state, payload) => { - const item = { ...payload, id: nanoid(10) }; - const list = insertSorted(state[key], item); - - return { ...state, [key]: list }; - }); - - // @ts-expect-error its ok. avoid extra fn creation - acc[key] = evt; - - return acc; - }, {} as SetApi); - - // @ts-expect-error its ok - const makeChildWithProps = (child) => - // @ts-expect-error its ok - memo((props) => { - const childProps = useMemo(() => child.fn(props), [props]); - - return ( - - - - ); - }); - - const slots = keys.reduce((acc, key) => { - const component = memo>((props) => { - const children = useStoreMap($slots, (x) => x[key]); - - return children.map((child) => { - if (isNil(child.fn)) { - return ( - - - - ); - } - - const ChildWithProps = makeChildWithProps(child); - - return ( - - - - ); - }); - }); - - acc[key] = component; - - return acc; - }, {} as Slots); - - const slotsApi = { - insert: { into: insertApi }, - }; - - return { slotsApi, Slots: slots }; -}; - -export { createSlotIdentifier, createSlots, type EmptyObject, type Payload }; diff --git a/tsconfig.json b/tsconfig.json index ba14f80..7a095b6 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -22,5 +22,5 @@ "emitDeclarationOnly": true, "jsx": "react" }, - "exclude": ["**/__tests__/**", "*.config.ts", "node_modules", "dist"] + "exclude": ["**/__tests__/**", "*.config.ts", "node_modules", "dist", "sandbox.tsx"] } From 80771bbe5bf8c32c0b6dc6e95b4410a4b9ff28f8 Mon Sep 17 00:00:00 2001 From: Viktor Pasynok Date: Tue, 4 Nov 2025 20:59:28 +0700 Subject: [PATCH 04/22] feat: enhance slot insertion with conditional triggers and introduce Payload type --- sandbox.tsx | 18 ++++++++++++------ src/__tests__/payload.spec-d.tsx | 2 +- src/index.tsx | 32 ++++++++++++++++++++++++-------- src/payload.ts | 14 ++++++++++++++ 4 files changed, 51 insertions(+), 15 deletions(-) create mode 100644 src/payload.ts diff --git a/sandbox.tsx b/sandbox.tsx index cdf1ca9..4b99258 100644 --- a/sandbox.tsx +++ b/sandbox.tsx @@ -1,15 +1,21 @@ -import { createEvent, sample } from 'effector'; +import { createGate } from 'effector-react'; +import React from 'react'; import { createSlotIdentifier, createSlots } from './src'; const { slotsApi } = createSlots({ ConfirmScreenBottom: createSlotIdentifier<{ id: number }>(), } as const); -const evt = createEvent<{ id: number }>(); +const appGate = createGate(); -sample({ - clock: evt, - target: slotsApi.insert.into.ConfirmScreenBottom, +// todo: use `when` payload in `fn` if `when` passed +slotsApi.insert.into.ConfirmScreenBottom({ + when: [appGate.open], + fn: (__, y) => ({ id: y }), + component: (props) =>

Hello world! {props.id}

, }); -// slotsApi.on(evt).insert.into.ConfirmScreenBottom +slotsApi.insert.into.ConfirmScreenBottom({ + fn: (x) => x, + component: (props) =>

Hello world! {props.id}

, +}); diff --git a/src/__tests__/payload.spec-d.tsx b/src/__tests__/payload.spec-d.tsx index 47dca60..90ab774 100644 --- a/src/__tests__/payload.spec-d.tsx +++ b/src/__tests__/payload.spec-d.tsx @@ -1,7 +1,7 @@ import React from 'react'; import { expectTypeOf } from 'vitest'; import { EmptyObject } from '../helpers'; -import { createSlotIdentifier, createSlots } from '../init'; +import { createSlotIdentifier, createSlots } from '../index'; const slotId = createSlotIdentifier<{ text: string }>(); const noPropsSlot = createSlotIdentifier(); diff --git a/src/index.tsx b/src/index.tsx index 64cfa37..5d0e873 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -1,8 +1,9 @@ -import { createEvent, createStore } from 'effector'; +import { createEvent, createStore, type EventCallable } from 'effector'; import { useStoreMap } from 'effector-react'; import { nanoid } from 'nanoid'; import React, { memo, type FunctionComponent } from 'react'; import { insertSorted, isNil, makeChildWithProps, type EmptyObject, type Entries } from './helpers'; +import type { Payload } from './payload'; type CreateSlotIdentifier = () => (_: T) => T; @@ -10,12 +11,6 @@ const createSlotIdentifier: CreateSlotIdentifier = () => (props) => props; type SlotFunction = (_: T) => T; -type Payload = (params: { - component: (props: unknown extends R ? EmptyObject : R extends void ? EmptyObject : R) => React.JSX.Element; - fn?: (arg: T) => R; - order?: number; -}) => void; - const createSlots = >>(config: T) => { const entries = Object.entries(config) as Entries; const keys = entries.map(([k]) => k); @@ -45,7 +40,28 @@ const createSlots = >>(config: T) => const insertApi = keys.reduce((acc, key) => { const insert = createEvent>>[0]>(); - $slots.on(insert, (state, payload) => { + const unwatch = insert.watch((payload) => { + if (isNil(payload.when)) { + unwatch(); + + return; + } + + const triggers = Array.isArray(payload.when) ? payload.when : [payload.when]; + + triggers.forEach((trigger) => { + trigger.watch(() => { + const { when, ...data } = payload; + insert(data); + }); + }); + + unwatch(); + }); + + const immediateInsert = insert.filter({ fn: (x) => isNil(x.when) }); + + $slots.on(immediateInsert, (state, payload) => { const item = { ...payload, id: nanoid(10) }; const list = insertSorted(state[key], item); diff --git a/src/payload.ts b/src/payload.ts new file mode 100644 index 0000000..363efbb --- /dev/null +++ b/src/payload.ts @@ -0,0 +1,14 @@ +import type { EventCallable, EventPayload } from 'effector'; +import type { EmptyObject } from './helpers'; + +type ExtractWhenPayload = + T extends EventCallable ? P : T extends EventCallable[] ? EventPayload : never; + +type Payload = | EventCallable[] | undefined = undefined>(params: { + component: (props: unknown extends R ? EmptyObject : R extends void ? EmptyObject : R) => React.JSX.Element; + fn?: W extends undefined ? (arg: T) => R : (arg: T, whenPayload: ExtractWhenPayload>) => R; + order?: number; + when?: W; +}) => void; + +export type { Payload }; From 4808e48374e0e63829402bb9c834345520a4ad4c Mon Sep 17 00:00:00 2001 From: Viktor Pasynok Date: Tue, 4 Nov 2025 21:46:26 +0700 Subject: [PATCH 05/22] fix: ensure proper payload handling in slot insertion --- sandbox.tsx | 5 ++-- src/__tests__/payload.spec-d.tsx | 48 ++++++++++++++++++++++++++++---- src/payload.ts | 2 +- 3 files changed, 47 insertions(+), 8 deletions(-) diff --git a/sandbox.tsx b/sandbox.tsx index 4b99258..a8ab436 100644 --- a/sandbox.tsx +++ b/sandbox.tsx @@ -1,3 +1,4 @@ +import { createEvent } from 'effector'; import { createGate } from 'effector-react'; import React from 'react'; import { createSlotIdentifier, createSlots } from './src'; @@ -6,12 +7,12 @@ const { slotsApi } = createSlots({ ConfirmScreenBottom: createSlotIdentifier<{ id: number }>(), } as const); -const appGate = createGate(); +const appGate = createGate(); // todo: use `when` payload in `fn` if `when` passed slotsApi.insert.into.ConfirmScreenBottom({ when: [appGate.open], - fn: (__, y) => ({ id: y }), + fn: (__, y) => ({ id: Number(y) }), component: (props) =>

Hello world! {props.id}

, }); diff --git a/src/__tests__/payload.spec-d.tsx b/src/__tests__/payload.spec-d.tsx index 90ab774..722f4f3 100644 --- a/src/__tests__/payload.spec-d.tsx +++ b/src/__tests__/payload.spec-d.tsx @@ -1,13 +1,15 @@ +import { createEvent } from 'effector'; import React from 'react'; import { expectTypeOf } from 'vitest'; import { EmptyObject } from '../helpers'; import { createSlotIdentifier, createSlots } from '../index'; -const slotId = createSlotIdentifier<{ text: string }>(); +const slotWithProps = createSlotIdentifier<{ text: string }>(); const noPropsSlot = createSlotIdentifier(); +const signal = createEvent(); const { slotsApi } = createSlots({ - Top: slotId, + Top: slotWithProps, Bottom: noPropsSlot, }); @@ -20,16 +22,39 @@ slotsApi.insert.into.Top({ }); slotsApi.insert.into.Top({ + when: signal, + fn: (__, signalPayload) => ({ text: String(signalPayload) }), component: (props) => { - expectTypeOf(props); + expectTypeOf<{ text: string }>(props); return
; }, }); slotsApi.insert.into.Top({ - fn: () => {}, + when: [signal], + fn: (__, signalPayload) => ({ text: String(signalPayload) }), component: (props) => { - expectTypeOf(props); + expectTypeOf<{ text: string }>(props); + return
; + }, +}); + +slotsApi.insert.into.Top({ + when: [signal, createEvent()], + fn: (__, signalPayload) => { + const text = Array.isArray(signalPayload) ? signalPayload[0] : String(signalPayload); + + return { text }; + }, + component: (props) => { + expectTypeOf<{ text: string }>(props); + return
; + }, +}); + +slotsApi.insert.into.Top({ + component: (props) => { + expectTypeOf<{ text: string }>(props); return
; }, }); @@ -46,3 +71,16 @@ slotsApi.insert.into.Top({ // @ts-expect-error component: (_: { wrong: number }) =>
, }); + +slotsApi.insert.into.Top({ + // @ts-expect-error + fn: () => {}, + component: () =>
, +}); + +slotsApi.insert.into.Top({ + when: signal, + // @ts-expect-error + fn: (__, signalPayload) => ({ text: signalPayload }), + component: () =>
, +}); diff --git a/src/payload.ts b/src/payload.ts index 363efbb..f0ce372 100644 --- a/src/payload.ts +++ b/src/payload.ts @@ -4,7 +4,7 @@ import type { EmptyObject } from './helpers'; type ExtractWhenPayload = T extends EventCallable ? P : T extends EventCallable[] ? EventPayload : never; -type Payload = | EventCallable[] | undefined = undefined>(params: { +type Payload = | EventCallable[] | undefined = undefined>(params: { component: (props: unknown extends R ? EmptyObject : R extends void ? EmptyObject : R) => React.JSX.Element; fn?: W extends undefined ? (arg: T) => R : (arg: T, whenPayload: ExtractWhenPayload>) => R; order?: number; From c72ccf29a154a4a7326379875c40371572377e2d Mon Sep 17 00:00:00 2001 From: Viktor Pasynok Date: Tue, 4 Nov 2025 21:54:47 +0700 Subject: [PATCH 06/22] refactor: remove unused EventCallable import from index.tsx --- src/index.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/index.tsx b/src/index.tsx index 5d0e873..0bd4a42 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -1,8 +1,8 @@ -import { createEvent, createStore, type EventCallable } from 'effector'; +import { createEvent, createStore } from 'effector'; import { useStoreMap } from 'effector-react'; import { nanoid } from 'nanoid'; import React, { memo, type FunctionComponent } from 'react'; -import { insertSorted, isNil, makeChildWithProps, type EmptyObject, type Entries } from './helpers'; +import { insertSorted, isNil, makeChildWithProps, type Entries } from './helpers'; import type { Payload } from './payload'; type CreateSlotIdentifier = () => (_: T) => T; From c228dec6fdd0d4f769d16b158feeb3406e6aee89 Mon Sep 17 00:00:00 2001 From: Viktor Pasynok Date: Tue, 4 Nov 2025 22:00:39 +0700 Subject: [PATCH 07/22] refactor: update trigger watch to handle payload function and trigger data --- src/index.tsx | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/index.tsx b/src/index.tsx index 0bd4a42..43a72e6 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -50,9 +50,12 @@ const createSlots = >>(config: T) => const triggers = Array.isArray(payload.when) ? payload.when : [payload.when]; triggers.forEach((trigger) => { - trigger.watch(() => { - const { when, ...data } = payload; - insert(data); + trigger.watch((triggerPayload) => { + const { when, fn, ...data } = payload; + + const wrappedFn = fn ? (props: any) => fn(props, triggerPayload) : undefined; + + insert({ ...data, fn: wrappedFn }); }); }); From 2c025b384ee072233f89ffa7a2c3cdccfc448ea0 Mon Sep 17 00:00:00 2001 From: Viktor Pasynok Date: Tue, 4 Nov 2025 23:01:24 +0700 Subject: [PATCH 08/22] chore: update size limit and version, refactor imports in helpers and payload --- .size-limit.json | 2 +- package.json | 2 +- sandbox.tsx | 1 - src/helpers.tsx | 2 +- src/payload.ts | 7 +++---- 5 files changed, 6 insertions(+), 8 deletions(-) diff --git a/.size-limit.json b/.size-limit.json index 212fefb..9fa916f 100644 --- a/.size-limit.json +++ b/.size-limit.json @@ -1 +1 @@ -[{ "path": "dist/index.js", "limit": "800 B" }] +[{ "path": "dist/index.js", "limit": "850 B" }] diff --git a/package.json b/package.json index e6c8256..592ed19 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@grlt-hub/react-slots", - "version": "1.1.0", + "version": "1.2.0-next.1", "type": "module", "private": false, "main": "dist/index.js", diff --git a/sandbox.tsx b/sandbox.tsx index a8ab436..f98a21e 100644 --- a/sandbox.tsx +++ b/sandbox.tsx @@ -9,7 +9,6 @@ const { slotsApi } = createSlots({ const appGate = createGate(); -// todo: use `when` payload in `fn` if `when` passed slotsApi.insert.into.ConfirmScreenBottom({ when: [appGate.open], fn: (__, y) => ({ id: Number(y) }), diff --git a/src/helpers.tsx b/src/helpers.tsx index bfcc14a..fc213d6 100644 --- a/src/helpers.tsx +++ b/src/helpers.tsx @@ -1,4 +1,4 @@ -import React, { useMemo } from 'react'; +import React, { memo, useMemo } from 'react'; const isNil = (x: T | undefined | null): x is undefined | null => x === null || x === undefined; diff --git a/src/payload.ts b/src/payload.ts index f0ce372..78c0ae5 100644 --- a/src/payload.ts +++ b/src/payload.ts @@ -1,10 +1,9 @@ -import type { EventCallable, EventPayload } from 'effector'; +import type { Event, EventPayload } from 'effector'; import type { EmptyObject } from './helpers'; -type ExtractWhenPayload = - T extends EventCallable ? P : T extends EventCallable[] ? EventPayload : never; +type ExtractWhenPayload = T extends Event ? P : T extends Event[] ? EventPayload : never; -type Payload = | EventCallable[] | undefined = undefined>(params: { +type Payload = | Event[] | undefined = undefined>(params: { component: (props: unknown extends R ? EmptyObject : R extends void ? EmptyObject : R) => React.JSX.Element; fn?: W extends undefined ? (arg: T) => R : (arg: T, whenPayload: ExtractWhenPayload>) => R; order?: number; From f680cdf8a6cf22a6f65ecd6a14cf726610998539 Mon Sep 17 00:00:00 2001 From: Viktor Pasynok Date: Tue, 4 Nov 2025 23:20:17 +0700 Subject: [PATCH 09/22] refactor: rename fn to mapProps for consistency in slot insertion and payload handling --- sandbox.tsx | 4 ++-- src/__tests__/payload.spec-d.tsx | 14 +++++++------- src/helpers.tsx | 2 +- src/index.tsx | 8 ++++---- src/payload.ts | 2 +- 5 files changed, 15 insertions(+), 15 deletions(-) diff --git a/sandbox.tsx b/sandbox.tsx index f98a21e..c7c99a8 100644 --- a/sandbox.tsx +++ b/sandbox.tsx @@ -11,11 +11,11 @@ const appGate = createGate(); slotsApi.insert.into.ConfirmScreenBottom({ when: [appGate.open], - fn: (__, y) => ({ id: Number(y) }), + mapProps: (__, y) => ({ id: Number(y) }), component: (props) =>

Hello world! {props.id}

, }); slotsApi.insert.into.ConfirmScreenBottom({ - fn: (x) => x, + mapProps: (x) => x, component: (props) =>

Hello world! {props.id}

, }); diff --git a/src/__tests__/payload.spec-d.tsx b/src/__tests__/payload.spec-d.tsx index 722f4f3..e7eabbe 100644 --- a/src/__tests__/payload.spec-d.tsx +++ b/src/__tests__/payload.spec-d.tsx @@ -14,7 +14,7 @@ const { slotsApi } = createSlots({ }); slotsApi.insert.into.Top({ - fn: (data) => ({ text: data.text }), + mapProps: (data) => ({ text: data.text }), component: (props) => { expectTypeOf<{ text: string }>(props); return
; @@ -23,7 +23,7 @@ slotsApi.insert.into.Top({ slotsApi.insert.into.Top({ when: signal, - fn: (__, signalPayload) => ({ text: String(signalPayload) }), + mapProps: (__, signalPayload) => ({ text: String(signalPayload) }), component: (props) => { expectTypeOf<{ text: string }>(props); return
; @@ -32,7 +32,7 @@ slotsApi.insert.into.Top({ slotsApi.insert.into.Top({ when: [signal], - fn: (__, signalPayload) => ({ text: String(signalPayload) }), + mapProps: (__, signalPayload) => ({ text: String(signalPayload) }), component: (props) => { expectTypeOf<{ text: string }>(props); return
; @@ -41,7 +41,7 @@ slotsApi.insert.into.Top({ slotsApi.insert.into.Top({ when: [signal, createEvent()], - fn: (__, signalPayload) => { + mapProps: (__, signalPayload) => { const text = Array.isArray(signalPayload) ? signalPayload[0] : String(signalPayload); return { text }; @@ -67,20 +67,20 @@ slotsApi.insert.into.Bottom({ }); slotsApi.insert.into.Top({ - fn: (data) => ({ text: data.text }), + mapProps: (data) => ({ text: data.text }), // @ts-expect-error component: (_: { wrong: number }) =>
, }); slotsApi.insert.into.Top({ // @ts-expect-error - fn: () => {}, + mapProps: () => {}, component: () =>
, }); slotsApi.insert.into.Top({ when: signal, // @ts-expect-error - fn: (__, signalPayload) => ({ text: signalPayload }), + mapProps: (__, signalPayload) => ({ text: signalPayload }), component: () =>
, }); diff --git a/src/helpers.tsx b/src/helpers.tsx index fc213d6..41af7c3 100644 --- a/src/helpers.tsx +++ b/src/helpers.tsx @@ -18,7 +18,7 @@ type Entries = { const makeChildWithProps = (child) => // @ts-expect-error its ok memo((props) => { - const childProps = useMemo(() => child.fn(props), [props]); + const childProps = useMemo(() => child.mapProps(props), [props]); return ; }); diff --git a/src/index.tsx b/src/index.tsx index 43a72e6..c5a5513 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -51,11 +51,11 @@ const createSlots = >>(config: T) => triggers.forEach((trigger) => { trigger.watch((triggerPayload) => { - const { when, fn, ...data } = payload; + const { when, mapProps, ...data } = payload; - const wrappedFn = fn ? (props: any) => fn(props, triggerPayload) : undefined; + const wrappedFn = mapProps ? (props: any) => mapProps(props, triggerPayload) : undefined; - insert({ ...data, fn: wrappedFn }); + insert({ ...data, mapProps: wrappedFn }); }); }); @@ -82,7 +82,7 @@ const createSlots = >>(config: T) => const slotChildren = useStoreMap($slots, (x) => x[key]); return slotChildren.map((child) => { - if (isNil(child.fn)) { + if (isNil(child.mapProps)) { return ( diff --git a/src/payload.ts b/src/payload.ts index 78c0ae5..86bdb90 100644 --- a/src/payload.ts +++ b/src/payload.ts @@ -5,7 +5,7 @@ type ExtractWhenPayload = T extends Event ? P : T extends Event type Payload = | Event[] | undefined = undefined>(params: { component: (props: unknown extends R ? EmptyObject : R extends void ? EmptyObject : R) => React.JSX.Element; - fn?: W extends undefined ? (arg: T) => R : (arg: T, whenPayload: ExtractWhenPayload>) => R; + mapProps?: W extends undefined ? (arg: T) => R : (arg: T, whenPayload: ExtractWhenPayload>) => R; order?: number; when?: W; }) => void; From 33540ebba8c874e4661afbe90a2b33a771cf11e4 Mon Sep 17 00:00:00 2001 From: Viktor Pasynok Date: Tue, 4 Nov 2025 23:33:30 +0700 Subject: [PATCH 10/22] chore: update version to 2.0.0-next.0, enhance description, and add 'when' parameter for deferred slot insertion --- CHANGELOG.md | 10 ++++++++++ package-lock.json | 12 ++++++------ package.json | 27 +++++++++++++++++---------- 3 files changed, 33 insertions(+), 16 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ab4349f..a503c59 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,16 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](http://semver.org). +## 2.0.0 + +### Changed + +- renamed `fn` to `mapProps` + +### Added + +- `when` parameter to defer slot insertion until specified Effector events fire + ## 1.1.0 ### Added diff --git a/package-lock.json b/package-lock.json index f63d78d..62eb75f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,16 +1,17 @@ { "name": "@grlt-hub/react-slots", - "version": "1.1.0", + "version": "2.0.0-next.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@grlt-hub/react-slots", - "version": "1.1.0", + "version": "2.0.0-next.0", "license": "MIT", "devDependencies": { "@rslib/core": "0.17.0", "@size-limit/preset-small-lib": "11.2.0", + "@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "clean-publish": "6.0.1", "prettier": "3.6.2", "prettier-plugin-organize-imports": "4.3.0", @@ -20,7 +21,6 @@ "vitest": "4.0.6" }, "peerDependencies": { - "@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "effector": "23", "effector-react": "23", "nanoid": "*", @@ -1407,8 +1407,8 @@ "version": "19.2.2", "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.2.tgz", "integrity": "sha512-6mDvHUFSjyT2B2yeNx2nUgMxh9LtOWvkhIU3uePn2I2oyNymUAX1NIsdgviM4CH+JSrp2D2hsMvJOkxY+0wNRA==", + "dev": true, "license": "MIT", - "peer": true, "dependencies": { "csstype": "^3.0.2" } @@ -1606,8 +1606,8 @@ "version": "3.1.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", - "license": "MIT", - "peer": true + "dev": true, + "license": "MIT" }, "node_modules/debug": { "version": "4.4.3", diff --git a/package.json b/package.json index 592ed19..74a2390 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@grlt-hub/react-slots", - "version": "1.2.0-next.1", + "version": "2.0.0-next.0", "type": "module", "private": false, "main": "dist/index.js", @@ -12,6 +12,7 @@ "devDependencies": { "@rslib/core": "0.17.0", "@size-limit/preset-small-lib": "11.2.0", + "@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "clean-publish": "6.0.1", "prettier": "3.6.2", "prettier-plugin-organize-imports": "4.3.0", @@ -21,7 +22,6 @@ "vitest": "4.0.6" }, "peerDependencies": { - "@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "effector": "23", "effector-react": "23", "nanoid": "*", @@ -39,19 +39,26 @@ "url": "https://github.com/grlt-hub/react-slots.git" }, "homepage": "https://github.com/grlt-hub/react-slots", - "description": "Bring the power of slots to your React components effortlessly.", + "description": "Declarative slot system for React. Build extensible, plugin-ready components with dynamic injection powered by Effector", "keywords": [ "grlt", "grlt-hub", "react", + "react-slots", "slots", - "slot-based-architecture", - "dynamic-content", + "slot-pattern", + "component-slots", + "plugin-architecture", + "extensible-components", "component-injection", - "modular-components", - "react-library", - "ui-framework", - "content-management", - "frontend" + "dynamic-injection", + "event-driven", + "modular-ui", + "type-safe", + "composition", + "composable", + "lightweight", + "deferred-rendering", + "lazy-components" ] } From 1faa7f28fdd8ea395d0cc8c789ff0dc6249a6e56 Mon Sep 17 00:00:00 2001 From: Viktor Pasynok Date: Wed, 5 Nov 2025 00:45:13 +0700 Subject: [PATCH 11/22] feat: add logo and create README_NEW.md for project documentation --- README_NEW.md | 156 ++++++++++++++++++++++++++++++++++++++++++++++++++ logo.webp | Bin 0 -> 108088 bytes 2 files changed, 156 insertions(+) create mode 100644 README_NEW.md create mode 100644 logo.webp diff --git a/README_NEW.md b/README_NEW.md new file mode 100644 index 0000000..3103135 --- /dev/null +++ b/README_NEW.md @@ -0,0 +1,156 @@ +

+ React Slots +

+ +# React Slots + +**Build extensible React components with slot-based architecture.** Define extension points where plugins and third-party code can inject content. + +## What are slots? + +**Slots** are named extension points in a component where content can be injected from outside. + +Vue example: + +```vue + + + + + + + +``` + +## The problem in React + +React doesn't have a built-in slot system. This creates challenges when building **extensible architectures** where different parts of your app (or plugins) need to inject content into predefined locations. + +### Example: Admin dashboard with plugins + +You're building an admin dashboard. Plugins should be able to add widgets to the sidebar without modifying the core `Sidebar` component: + +```tsx +// Sidebar.tsx - core component (shouldn't change when plugins are added) +export const Sidebar = () => { + return ( + + ); +}; + +// plugin-analytics/index.ts - separate package +// This plugin wants to add analytics widget to sidebar +// How??? πŸ€·β€β™‚οΈ +``` + +### Standard approaches are awkward + +- Collecting everything in parent component - tight coupling, parent must know all plugins +- Context with manual management - lots of boilerplate per extension point +- Passing render functions through props - verbose, non-intuitive API + +## The solution + +**With `react-slots`, define extension points once and inject components from anywhere:** + +```tsx +// Sidebar.tsx - define the slot +import { createSlots, createSlotIdentifier } from '@grlt-hub/react-slots'; + +export const { slotsApi, Slots } = createSlots({ + Widgets: createSlotIdentifier(), +} as const); + +export const Sidebar = () => { + return ( + + ); +}; + +// plugin-analytics/index.ts - inject from anywhere! +import { slotsApi } from './Sidebar'; + +slotsApi.insert.into.Widgets({ + component: () => , +}); + +// plugin-user-stats/index.ts - another plugin +import { slotsApi } from './Sidebar'; + +slotsApi.insert.into.Widgets({ + component: () => , +}); +``` + +### Result + +```tsx + +``` + +No props drilling, no boilerplate - just define slots and inject content from anywhere in your codebase. + +## Installation + +```sh +npm i @grlt-hub/react-slots +# or +pnpm add @grlt-hub/react-slots +# or +bun add @grlt-hub/react-slots +``` + +### Peer dependencies + +- `react` ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 +- `effector` 23 +- `effector-react` 23 +- `nanoid` \* + +## Quick Start + +Here's a minimal working example: + +```tsx +import { createSlots, createSlotIdentifier } from '@grlt-hub/react-slots'; + +// 1. Create slots +const { slotsApi, Slots } = createSlots({ + Footer: createSlotIdentifier(), +} as const); + +// 2. Use slot in your component +const App = () => ( +
+

My App

+ +
+); + +// 3. Insert content into the slot +slotsApi.insert.into.Footer({ + component: () =>

Β© 2024 My Company

, +}); + +// Result: +//
+//

My App

+//

Β© 2024 My Company

+//
+``` diff --git a/logo.webp b/logo.webp new file mode 100644 index 0000000000000000000000000000000000000000..e988259aad35a04d6d0315810fe2cad52823b7f1 GIT binary patch literal 108088 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�Y@IkqXxxhqB(GvreNA4bH#PvDdh$eil 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 Date: Wed, 5 Nov 2025 00:56:00 +0700 Subject: [PATCH 12/22] refactor: simplify Sidebar component syntax and update README_NEW.md with correct package name --- README_NEW.md | 34 +++++++++++++++------------------- 1 file changed, 15 insertions(+), 19 deletions(-) diff --git a/README_NEW.md b/README_NEW.md index 3103135..4db242b 100644 --- a/README_NEW.md +++ b/README_NEW.md @@ -38,14 +38,12 @@ You're building an admin dashboard. Plugins should be able to add widgets to the ```tsx // Sidebar.tsx - core component (shouldn't change when plugins are added) -export const Sidebar = () => { - return ( - - ); -}; +export const Sidebar = () => ( + +); // plugin-analytics/index.ts - separate package // This plugin wants to add analytics widget to sidebar @@ -60,7 +58,7 @@ export const Sidebar = () => { ## The solution -**With `react-slots`, define extension points once and inject components from anywhere:** +**With `@grlt-hub/react-slots`, define extension points once and inject components from anywhere:** ```tsx // Sidebar.tsx - define the slot @@ -70,14 +68,12 @@ export const { slotsApi, Slots } = createSlots({ Widgets: createSlotIdentifier(), } as const); -export const Sidebar = () => { - return ( - - ); -}; +export const Sidebar = () => ( + +); // plugin-analytics/index.ts - inject from anywhere! import { slotsApi } from './Sidebar'; @@ -145,12 +141,12 @@ const App = () => ( // 3. Insert content into the slot slotsApi.insert.into.Footer({ - component: () =>

Β© 2024 My Company

, + component: () =>

Β© 1955–1985–2015 Outatime Corp.

, }); // Result: //
//

My App

-//

Β© 2024 My Company

+//

Β© 1955–1985–2015 Outatime Corp.

//
``` From 75fd95c5e64e61430457714d2aefa7e5008662d1 Mon Sep 17 00:00:00 2001 From: Viktor Pasynok Date: Wed, 5 Nov 2025 01:07:17 +0700 Subject: [PATCH 13/22] docs: update README_NEW.md to include TypeScript types and add how-to guides for passing props to inserted components --- README_NEW.md | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/README_NEW.md b/README_NEW.md index 4db242b..5733499 100644 --- a/README_NEW.md +++ b/README_NEW.md @@ -112,6 +112,8 @@ pnpm add @grlt-hub/react-slots bun add @grlt-hub/react-slots ``` +TypeScript types are included out of the box. + ### Peer dependencies - `react` ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 @@ -150,3 +152,22 @@ slotsApi.insert.into.Footer({ //

Β© 1955–1985–2015 Outatime Corp.

//
``` + +## How-to Guides + +### Pass props to inserted components + +```tsx +// Define slot with typed props +const { slotsApi, Slots } = createSlots({ + UserPanel: createSlotIdentifier<{ userId: number }>(), +} as const); + +// Use in component +; + +// Insert component - receives props automatically +slotsApi.insert.into.UserPanel({ + component: (props) => , +}); +``` From 9b73bbb540b4cbcd46d2321d3314474073328750 Mon Sep 17 00:00:00 2001 From: Viktor Pasynok Date: Wed, 5 Nov 2025 01:15:32 +0700 Subject: [PATCH 14/22] refactor: update Payload type to support multiple mapProps signatures and improve trigger handling in slot insertion --- src/__tests__/payload.spec-d.tsx | 21 ++++++++++++--------- src/index.tsx | 3 +++ src/payload.ts | 31 +++++++++++++++++++++++++------ 3 files changed, 40 insertions(+), 15 deletions(-) diff --git a/src/__tests__/payload.spec-d.tsx b/src/__tests__/payload.spec-d.tsx index e7eabbe..083f052 100644 --- a/src/__tests__/payload.spec-d.tsx +++ b/src/__tests__/payload.spec-d.tsx @@ -67,20 +67,23 @@ slotsApi.insert.into.Bottom({ }); slotsApi.insert.into.Top({ - mapProps: (data) => ({ text: data.text }), - // @ts-expect-error - component: (_: { wrong: number }) =>
, -}); - -slotsApi.insert.into.Top({ - // @ts-expect-error mapProps: () => {}, component: () =>
, }); slotsApi.insert.into.Top({ when: signal, + mapProps: (slotPayload, signalPayload) => ({ data: { signalPayload, slotPayload } }), + component: (props) => { + expectTypeOf<{ + data: { slotPayload: { text: string }; signalPayload: number }; + }>(props); + return
; + }, +}); + +slotsApi.insert.into.Top({ + mapProps: (data) => ({ text: data.text }), // @ts-expect-error - mapProps: (__, signalPayload) => ({ text: signalPayload }), - component: () =>
, + component: (_: { wrong: number }) =>
, }); diff --git a/src/index.tsx b/src/index.tsx index c5a5513..7aec810 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -50,11 +50,14 @@ const createSlots = >>(config: T) => const triggers = Array.isArray(payload.when) ? payload.when : [payload.when]; triggers.forEach((trigger) => { + // @ts-expect-error trigger.watch((triggerPayload) => { const { when, mapProps, ...data } = payload; + // @ts-expect-error const wrappedFn = mapProps ? (props: any) => mapProps(props, triggerPayload) : undefined; + // @ts-expect-error insert({ ...data, mapProps: wrappedFn }); }); }); diff --git a/src/payload.ts b/src/payload.ts index 86bdb90..70c74cc 100644 --- a/src/payload.ts +++ b/src/payload.ts @@ -3,11 +3,30 @@ import type { EmptyObject } from './helpers'; type ExtractWhenPayload = T extends Event ? P : T extends Event[] ? EventPayload : never; -type Payload = | Event[] | undefined = undefined>(params: { - component: (props: unknown extends R ? EmptyObject : R extends void ? EmptyObject : R) => React.JSX.Element; - mapProps?: W extends undefined ? (arg: T) => R : (arg: T, whenPayload: ExtractWhenPayload>) => R; - order?: number; - when?: W; -}) => void; +type Payload = { + // When mapProps is provided with when + | Event[]>(params: { + component: (props: unknown extends R ? EmptyObject : R extends void ? EmptyObject : R) => React.JSX.Element; + mapProps: (arg: T, whenPayload: ExtractWhenPayload) => R; + order?: number; + when: W; + }): void; + + // When mapProps is provided without when + (params: { + component: (props: unknown extends R ? EmptyObject : R extends void ? EmptyObject : R) => React.JSX.Element; + mapProps: (arg: T) => R; + order?: number; + when?: undefined; + }): void; + + // When mapProps is not provided + (params: { + component: (props: unknown extends T ? EmptyObject : T extends void ? EmptyObject : T) => React.JSX.Element; + mapProps?: undefined; + order?: number; + when?: undefined; + }): void; +}; export type { Payload }; From 589849715f9a3d69d2ba11dfb20cc5d4deb7eb64 Mon Sep 17 00:00:00 2001 From: Viktor Pasynok Date: Wed, 5 Nov 2025 01:20:09 +0700 Subject: [PATCH 15/22] docs: add example for transforming props with mapProps in README_NEW.md --- README_NEW.md | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/README_NEW.md b/README_NEW.md index 5733499..b2d3293 100644 --- a/README_NEW.md +++ b/README_NEW.md @@ -171,3 +171,22 @@ slotsApi.insert.into.UserPanel({ component: (props) => , }); ``` + +### Transform props with `mapProps` + +```tsx +const { slotsApi, Slots } = createSlots({ + UserPanel: createSlotIdentifier<{ userId: number }>(), +} as const); + +; + +slotsApi.insert.into.UserPanel({ + // Transform userId into userName and isAdmin before passing to component + mapProps: (slotProps) => ({ + userName: getUserName(slotProps.userId), + isAdmin: checkAdmin(slotProps.userId), + }), + component: (props) => , +}); +``` From 511b96f3a8dc892f0c51c4d2e4b1bbdae8c3f3f3 Mon Sep 17 00:00:00 2001 From: Viktor Pasynok Date: Wed, 5 Nov 2025 01:25:22 +0700 Subject: [PATCH 16/22] docs: add section on control rendering order in README_NEW.md with examples --- README_NEW.md | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/README_NEW.md b/README_NEW.md index b2d3293..5edc6c0 100644 --- a/README_NEW.md +++ b/README_NEW.md @@ -190,3 +190,27 @@ slotsApi.insert.into.UserPanel({ component: (props) => , }); ``` + +### Control rendering order + +Components are inserted in any order, but rendered according to `order` value (lower numbers first): + +```tsx +// This is inserted first, but will render second +slotsApi.insert.into.Sidebar({ + component: () => , + order: 2, +}); + +// This is inserted second, but will render first +slotsApi.insert.into.Sidebar({ + component: () => , + order: 1, +}); + +// Result: +// <> +// ← order: 1 +// ← order: 2 +// +``` From 5fe16e4cec077fa02f85b8da17932a28c50e9a27 Mon Sep 17 00:00:00 2001 From: Viktor Pasynok Date: Wed, 5 Nov 2025 01:27:51 +0700 Subject: [PATCH 17/22] docs: add example for deferred component insertion in README_NEW.md --- README_NEW.md | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/README_NEW.md b/README_NEW.md index 5edc6c0..deb6bf2 100644 --- a/README_NEW.md +++ b/README_NEW.md @@ -214,3 +214,28 @@ slotsApi.insert.into.Sidebar({ // ← order: 2 // ``` + +### Defer insertion until event fires + +Wait for data to load before inserting component. The component won't render until the event fires: + +```tsx +import { createEvent } from 'effector'; + +const userLoaded = createEvent<{ id: number; name: string }>(); + +// Component will be inserted only after userLoaded fires +slotsApi.insert.into.Header({ + when: userLoaded, + mapProps: (slotProps, eventPayload) => ({ + userId: eventPayload.id, + userName: eventPayload.name, + }), + component: (props) => , +}); + +// Component is not rendered yet... + +// Later, when data arrives: +userLoaded({ id: 123, name: 'John' }); // NOW the component is inserted and rendered +``` From 28e0d4635a7a3dc8b0d1d9bc2e23bf4be3133f30 Mon Sep 17 00:00:00 2001 From: Viktor Pasynok Date: Wed, 5 Nov 2025 01:30:38 +0700 Subject: [PATCH 18/22] docs: add yarn installation command to README_NEW.md --- README_NEW.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README_NEW.md b/README_NEW.md index deb6bf2..a5e31db 100644 --- a/README_NEW.md +++ b/README_NEW.md @@ -110,6 +110,8 @@ npm i @grlt-hub/react-slots pnpm add @grlt-hub/react-slots # or bun add @grlt-hub/react-slots +# or +yarn add @grlt-hub/react-slots ``` TypeScript types are included out of the box. From 2b92869f0f7e33fab15691c1b517cf2009f32343 Mon Sep 17 00:00:00 2001 From: Viktor Pasynok Date: Wed, 5 Nov 2025 01:31:19 +0700 Subject: [PATCH 19/22] docs: add Community section with Telegram link to README_NEW.md --- README_NEW.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README_NEW.md b/README_NEW.md index a5e31db..3b83c64 100644 --- a/README_NEW.md +++ b/README_NEW.md @@ -241,3 +241,7 @@ slotsApi.insert.into.Header({ // Later, when data arrives: userLoaded({ id: 123, name: 'John' }); // NOW the component is inserted and rendered ``` + +## Community + +- [Telegram](https://t.me/grlt_hub_app_compose) From f196c7f5a24a709864701ea7082fb3f7d07d8eb1 Mon Sep 17 00:00:00 2001 From: Viktor Pasynok Date: Wed, 5 Nov 2025 01:39:11 +0700 Subject: [PATCH 20/22] docs: update CHANGELOG.md and README_NEW.md with examples for deferred component insertion and prop mapping --- CHANGELOG.md | 12 ++++++++++++ README_NEW.md | 10 +++++++--- 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a503c59..e8480e7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,18 @@ and this project adheres to [Semantic Versioning](http://semver.org). - `when` parameter to defer slot insertion until specified Effector events fire +```tsx +const userLoaded = createEvent<{ id: number }>(); + +slotsApi.insert.into.Header({ + when: userLoaded, // Wait for event + mapProps: (slotProps, whenPayload) => ({ userId: whenPayload.id }), + component: (props) => , +}); + +userLoaded({ id: 123 }); // Component inserted now +``` + ## 1.1.0 ### Added diff --git a/README_NEW.md b/README_NEW.md index 3b83c64..faafb94 100644 --- a/README_NEW.md +++ b/README_NEW.md @@ -217,6 +217,8 @@ slotsApi.insert.into.Sidebar({ // ``` +**Note:** Components with the same `order` value keep their insertion order and all of them are rendered. + ### Defer insertion until event fires Wait for data to load before inserting component. The component won't render until the event fires: @@ -229,9 +231,9 @@ const userLoaded = createEvent<{ id: number; name: string }>(); // Component will be inserted only after userLoaded fires slotsApi.insert.into.Header({ when: userLoaded, - mapProps: (slotProps, eventPayload) => ({ - userId: eventPayload.id, - userName: eventPayload.name, + mapProps: (slotProps, whenPayload) => ({ + userId: whenPayload.id, + userName: whenPayload.name, }), component: (props) => , }); @@ -242,6 +244,8 @@ slotsApi.insert.into.Header({ userLoaded({ id: 123, name: 'John' }); // NOW the component is inserted and rendered ``` +**Note:** You can pass an array of events `when: [event1, event2]` to wait for multiple events (component inserts after all of them fire). + ## Community - [Telegram](https://t.me/grlt_hub_app_compose) From 76a4c645e32f3dfc66ed52bec2e2ffa39838a177 Mon Sep 17 00:00:00 2001 From: Viktor Pasynok Date: Wed, 5 Nov 2025 01:43:13 +0700 Subject: [PATCH 21/22] docs: merge README_NEW.md content into README.md, enhancing documentation with slot examples, installation instructions, and community links --- README.md | 303 ++++++++++++++++++++++++++++---------------------- README_NEW.md | 251 ----------------------------------------- 2 files changed, 173 insertions(+), 381 deletions(-) delete mode 100644 README_NEW.md diff --git a/README.md b/README.md index cacbe0c..35944c6 100644 --- a/README.md +++ b/README.md @@ -1,208 +1,251 @@ -# React Slots +

+ React Slots +

-Bring the power of slots to your React components effortlessly. +# React Slots -## Table of Contents +**Build extensible React components with slot-based architecture.** Define extension points where plugins and third-party code can inject content. -- [Motivation](#motivation) -- [How does this library solve this problem?](#how-does-this-library-solve-this-problem) -- [Example](#example) -- [Installation](#installation) -- [How-to Guides](#how-to-guides) - - [How to Pass Props to Components Inserted into a Slot](#how-to-pass-props-to-components-inserted-into-a-slot) - - [How to Insert Multiple Components into a Slot](#how-to-insert-multiple-components-into-a-slot) - - [How to Manage the Order of Components in a Slot](#how-to-manage-the-order-of-components-in-a-slot) -- [Community](#community) +## What are slots? -## Motivation +**Slots** are named extension points in a component where content can be injected from outside. -In modern React applications, building reusable and **flexible** components is key to scaling efficiently. However, as the complexity of components increases, the need for a slot-based architecture becomes apparent. The concept of slots, familiar to developers from frameworks like Svelte and Vue, allows for seamless content injection and greater customization of component behavior. But in React, this pattern isn’t natively supported and often leads to verbose or suboptimal solutions. +Vue example: -## How does this library solve this problem? +```vue + + -`react-slots` introduces a streamlined way to implement slots, bringing familiar concepts into the React ecosystem with minimal effort. It provides developers with a clear and consistent API to define and use slots, enhancing flexibility while reducing boilerplate code. The library ensures components remain decoupled, making it easier to manage nested or complex content structures. + + + + +``` -## Example +## The problem in React -This example demonstrates how to create and use slots in React using the `@grlt-hub/react-slots` library. +React doesn't have a built-in slot system. This creates challenges when building **extensible architectures** where different parts of your app (or plugins) need to inject content into predefined locations. -### Code Breakdown +### Example: Admin dashboard with plugins -1. **Creating Slot Identifiers** +You're building an admin dashboard. Plugins should be able to add widgets to the sidebar without modifying the core `Sidebar` component: -```ts -import { createSlots, createSlotIdentifier } from '@grlt-hub/react-slots'; +```tsx +// Sidebar.tsx - core component (shouldn't change when plugins are added) +export const Sidebar = () => ( + +); -const slots = { - Bottom: createSlotIdentifier(), -} as const; +// plugin-analytics/index.ts - separate package +// This plugin wants to add analytics widget to sidebar +// How??? πŸ€·β€β™‚οΈ ``` -We import `createSlots` and `createSlotIdentifier` from the library. Then, we define a slots object, where each key represents a unique slot. In this case, we create a slot named Bottom. +### Standard approaches are awkward -2. **Creating the Slot API** +- Collecting everything in parent component - tight coupling, parent must know all plugins +- Context with manual management - lots of boilerplate per extension point +- Passing render functions through props - verbose, non-intuitive API -```ts -const { slotsApi: footerSlots, Slots: FooterSlots } = createSlots(slots); -``` +## The solution -`createSlots` takes the `slots` object and returns two values: +**With `@grlt-hub/react-slots`, define extension points once and inject components from anywhere:** -- `slotsApi` (renamed to `footerSlots`): an API for managing slot content. -- `Slots` (renamed to `FooterSlots`): a component used to render the slot content in the specified location. +```tsx +// Sidebar.tsx - define the slot +import { createSlots, createSlotIdentifier } from '@grlt-hub/react-slots'; -3. **Defining the Footer Component** +export const { slotsApi, Slots } = createSlots({ + Widgets: createSlotIdentifier(), +} as const); -```tsx -const Footer = () => ( -
- Hello - -
+export const Sidebar = () => ( + ); -``` -Using `footerSlots.insert.into.Bottom`, we insert content into the `Bottom` slot. Here, we add a component that renders `World`. +// plugin-analytics/index.ts - inject from anywhere! +import { slotsApi } from './Sidebar'; -### Result +slotsApi.insert.into.Widgets({ + component: () => , +}); -After executing the code, the rendered output will be: +// plugin-user-stats/index.ts - another plugin +import { slotsApi } from './Sidebar'; -```html -
- Hello - World -
+slotsApi.insert.into.Widgets({ + component: () => , +}); ``` -This way, the `@grlt-hub/react-slots` library provides an efficient way to define and use slots in React components, making content injection simple and flexible. +### Result + +```tsx + +``` + +No props drilling, no boilerplate - just define slots and inject content from anywhere in your codebase. ## Installation ```sh npm i @grlt-hub/react-slots # or -yarn add @grlt-hub/react-slots -# or pnpm add @grlt-hub/react-slots +# or +bun add @grlt-hub/react-slots +# or +yarn add @grlt-hub/react-slots ``` -## How-to Guides +TypeScript types are included out of the box. -### How to Pass Props to Components Inserted into a Slot +### Peer dependencies -In this guide, we'll walk through how to define and pass props to components inserted into a slot using `@grlt-hub/react-slots`. +- `react` ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 +- `effector` 23 +- `effector-react` 23 +- `nanoid` \* -#### Step 1: Define a Slot with Props +## Quick Start -You can specify the props a slot should accept by providing a type to `createSlotIdentifier`. For example, if you want a slot that requires a text prop, you can define it like this: +Here's a minimal working example: -```ts -import { createSlotIdentifier } from '@grlt-hub/react-slots'; +```tsx +import { createSlots, createSlotIdentifier } from '@grlt-hub/react-slots'; -const slots = { - Bottom: createSlotIdentifier<{ text: string }>(), -} as const; -``` +// 1. Create slots +const { slotsApi, Slots } = createSlots({ + Footer: createSlotIdentifier(), +} as const); + +// 2. Use slot in your component +const App = () => ( +
+

My App

+ +
+); -This type definition ensures that any usage of `` must include a `text` prop. +// 3. Insert content into the slot +slotsApi.insert.into.Footer({ + component: () =>

Β© 1955–1985–2015 Outatime Corp.

, +}); -#### Step 2: Use the Slot in Your Component +// Result: +//
+//

My App

+//

Β© 1955–1985–2015 Outatime Corp.

+//
+``` + +## How-to Guides -When you use the slot component in your layout, you must pass the required props directly: +### Pass props to inserted components ```tsx -const Footer = () => ( -
- Footer content - -
-); -``` +// Define slot with typed props +const { slotsApi, Slots } = createSlots({ + UserPanel: createSlotIdentifier<{ userId: number }>(), +} as const); -The `text` prop passed here will be provided to any component inserted into the `Bottom` slot. +// Use in component +; -#### Step 3: Insert a Component into the Slot +// Insert component - receives props automatically +slotsApi.insert.into.UserPanel({ + component: (props) => , +}); +``` -You use `footerSlots.insert.into.Bottom` to insert a component. The component will automatically receive the props passed to ``: +### Transform props with `mapProps` ```tsx -footerSlots.insert.into.Bottom({ - fn: ({ text }) => ({ doubleText: `${text} ${text}` }), - component: ({ doubleText }) =>

{doubleText}

, +const { slotsApi, Slots } = createSlots({ + UserPanel: createSlotIdentifier<{ userId: number }>(), +} as const); + +; + +slotsApi.insert.into.UserPanel({ + // Transform userId into userName and isAdmin before passing to component + mapProps: (slotProps) => ({ + userName: getUserName(slotProps.userId), + isAdmin: checkAdmin(slotProps.userId), + }), + component: (props) => , }); ``` -- `fn`: This function is optional. If provided, it receives the props from `` (e.g.,` { text }`) and allows you to transform them before passing them to `component`. In the example above, `fn` takes `text` and creates a new prop `doubleText`, which repeats the `text` value twice. -- **Without** `fn`: If `fn` is not provided, the props from `` are passed directly to component without any transformation, one-to-one. -- `component`: This function receives either the transformed props (if `fn` is used) or the original props and renders the component accordingly. - -This flexibility allows you to choose whether to modify props or pass them through unchanged, depending on your use case. - -### How to Insert Multiple Components into a Slot +### Control rendering order -Inserting multiple components into a slot is straightforward. You can call `footerSlots.insert.into.Bottom` multiple times to add different components. The components will be added in the order in which they are inserted. - -#### Example - -Here's how you can insert multiple components into the `Bottom` slot: +Components are inserted in any order, but rendered according to `order` value (lower numbers first): ```tsx -footerSlots.insert.into.Bottom({ - component: () =>

First Component

, +// This is inserted first, but will render second +slotsApi.insert.into.Sidebar({ + component: () => , + order: 2, }); -footerSlots.insert.into.Bottom({ - component: () =>

Second Component

, +// This is inserted second, but will render first +slotsApi.insert.into.Sidebar({ + component: () => , + order: 1, }); -``` - -In this example: - -- The first call to `footerSlots.insert.into.Bottom` inserts a component that renders `

First Component

`. -- The second call inserts a component that renders `

Second Component

`. - -The components will appear in the order they are inserted, so the rendered output will look like this: -```html -
First Component Second Component
+// Result: +// <> +// ← order: 1 +// ← order: 2 +// ``` -### How to Manage the Order of Components in a Slot +**Note:** Components with the same `order` value keep their insertion order and all of them are rendered. -You can control the order in which components are rendered within a slot using the optional `order` property. By default, components are added in the order they are inserted. However, you can specify a custom order to rearrange them. +### Defer insertion until event fires -#### Example - -Let's build on the previous example and introduce the order property: +Wait for data to load before inserting component. The component won't render until the event fires: ```tsx -footerSlots.insert.into.Bottom({ - component: () =>

First Component

, -}); - -footerSlots.insert.into.Bottom({ - component: () =>

Second Component

, - order: 0, +import { createEvent } from 'effector'; + +const userLoaded = createEvent<{ id: number; name: string }>(); + +// Component will be inserted only after userLoaded fires +slotsApi.insert.into.Header({ + when: userLoaded, + mapProps: (slotProps, whenPayload) => ({ + userId: whenPayload.id, + userName: whenPayload.name, + }), + component: (props) => , }); -``` - -- In this case, the first call inserts `

First Component

` without an `order` property, so it gets the default position. -- The second call inserts `

Second Component

` and specifies `order: 0`. This causes the "Second Component" to be rendered before the "First Component". -With the order property applied, the rendered output will look like this: +// Component is not rendered yet... -```html -
Second Component First Component
+// Later, when data arrives: +userLoaded({ id: 123, name: 'John' }); // NOW the component is inserted and rendered ``` -#### How the `order` Property Works - -- **Type**: `order` is always a number. -- **Default Behavior**: If `order` is not provided, the components are rendered in the order they are inserted. -- **Custom Order**: Components with a lower `order` value are rendered before those with a higher value. If multiple components have the same `order` value, they maintain the order of insertion. +**Note:** You can pass an array of events `when: [event1, event2]` - component inserts when **any** of them fires. Use [once](https://patronum.effector.dev/operators/once/) from `patronum` if you need one-time insertion. ## Community -- [Discord](https://discord.gg/Q4DFKnxp) - [Telegram](https://t.me/grlt_hub_app_compose) diff --git a/README_NEW.md b/README_NEW.md deleted file mode 100644 index faafb94..0000000 --- a/README_NEW.md +++ /dev/null @@ -1,251 +0,0 @@ -

- React Slots -

- -# React Slots - -**Build extensible React components with slot-based architecture.** Define extension points where plugins and third-party code can inject content. - -## What are slots? - -**Slots** are named extension points in a component where content can be injected from outside. - -Vue example: - -```vue - - - - - - - -``` - -## The problem in React - -React doesn't have a built-in slot system. This creates challenges when building **extensible architectures** where different parts of your app (or plugins) need to inject content into predefined locations. - -### Example: Admin dashboard with plugins - -You're building an admin dashboard. Plugins should be able to add widgets to the sidebar without modifying the core `Sidebar` component: - -```tsx -// Sidebar.tsx - core component (shouldn't change when plugins are added) -export const Sidebar = () => ( - -); - -// plugin-analytics/index.ts - separate package -// This plugin wants to add analytics widget to sidebar -// How??? πŸ€·β€β™‚οΈ -``` - -### Standard approaches are awkward - -- Collecting everything in parent component - tight coupling, parent must know all plugins -- Context with manual management - lots of boilerplate per extension point -- Passing render functions through props - verbose, non-intuitive API - -## The solution - -**With `@grlt-hub/react-slots`, define extension points once and inject components from anywhere:** - -```tsx -// Sidebar.tsx - define the slot -import { createSlots, createSlotIdentifier } from '@grlt-hub/react-slots'; - -export const { slotsApi, Slots } = createSlots({ - Widgets: createSlotIdentifier(), -} as const); - -export const Sidebar = () => ( - -); - -// plugin-analytics/index.ts - inject from anywhere! -import { slotsApi } from './Sidebar'; - -slotsApi.insert.into.Widgets({ - component: () => , -}); - -// plugin-user-stats/index.ts - another plugin -import { slotsApi } from './Sidebar'; - -slotsApi.insert.into.Widgets({ - component: () => , -}); -``` - -### Result - -```tsx - -``` - -No props drilling, no boilerplate - just define slots and inject content from anywhere in your codebase. - -## Installation - -```sh -npm i @grlt-hub/react-slots -# or -pnpm add @grlt-hub/react-slots -# or -bun add @grlt-hub/react-slots -# or -yarn add @grlt-hub/react-slots -``` - -TypeScript types are included out of the box. - -### Peer dependencies - -- `react` ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 -- `effector` 23 -- `effector-react` 23 -- `nanoid` \* - -## Quick Start - -Here's a minimal working example: - -```tsx -import { createSlots, createSlotIdentifier } from '@grlt-hub/react-slots'; - -// 1. Create slots -const { slotsApi, Slots } = createSlots({ - Footer: createSlotIdentifier(), -} as const); - -// 2. Use slot in your component -const App = () => ( -
-

My App

- -
-); - -// 3. Insert content into the slot -slotsApi.insert.into.Footer({ - component: () =>

Β© 1955–1985–2015 Outatime Corp.

, -}); - -// Result: -//
-//

My App

-//

Β© 1955–1985–2015 Outatime Corp.

-//
-``` - -## How-to Guides - -### Pass props to inserted components - -```tsx -// Define slot with typed props -const { slotsApi, Slots } = createSlots({ - UserPanel: createSlotIdentifier<{ userId: number }>(), -} as const); - -// Use in component -; - -// Insert component - receives props automatically -slotsApi.insert.into.UserPanel({ - component: (props) => , -}); -``` - -### Transform props with `mapProps` - -```tsx -const { slotsApi, Slots } = createSlots({ - UserPanel: createSlotIdentifier<{ userId: number }>(), -} as const); - -; - -slotsApi.insert.into.UserPanel({ - // Transform userId into userName and isAdmin before passing to component - mapProps: (slotProps) => ({ - userName: getUserName(slotProps.userId), - isAdmin: checkAdmin(slotProps.userId), - }), - component: (props) => , -}); -``` - -### Control rendering order - -Components are inserted in any order, but rendered according to `order` value (lower numbers first): - -```tsx -// This is inserted first, but will render second -slotsApi.insert.into.Sidebar({ - component: () => , - order: 2, -}); - -// This is inserted second, but will render first -slotsApi.insert.into.Sidebar({ - component: () => , - order: 1, -}); - -// Result: -// <> -// ← order: 1 -// ← order: 2 -// -``` - -**Note:** Components with the same `order` value keep their insertion order and all of them are rendered. - -### Defer insertion until event fires - -Wait for data to load before inserting component. The component won't render until the event fires: - -```tsx -import { createEvent } from 'effector'; - -const userLoaded = createEvent<{ id: number; name: string }>(); - -// Component will be inserted only after userLoaded fires -slotsApi.insert.into.Header({ - when: userLoaded, - mapProps: (slotProps, whenPayload) => ({ - userId: whenPayload.id, - userName: whenPayload.name, - }), - component: (props) => , -}); - -// Component is not rendered yet... - -// Later, when data arrives: -userLoaded({ id: 123, name: 'John' }); // NOW the component is inserted and rendered -``` - -**Note:** You can pass an array of events `when: [event1, event2]` to wait for multiple events (component inserts after all of them fire). - -## Community - -- [Telegram](https://t.me/grlt_hub_app_compose) From 89c3050ae0ed0371f47d647d7078f7ea8e7cd1e8 Mon Sep 17 00:00:00 2001 From: Viktor Pasynok Date: Wed, 5 Nov 2025 02:01:35 +0700 Subject: [PATCH 22/22] chore: update package version to 2.0.0 and upgrade vitest and related dependencies to 4.0.7 --- package-lock.json | 88 +++++++++++++++++++++++------------------------ package.json | 4 +-- 2 files changed, 46 insertions(+), 46 deletions(-) diff --git a/package-lock.json b/package-lock.json index 62eb75f..1c6272c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@grlt-hub/react-slots", - "version": "2.0.0-next.0", + "version": "2.0.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@grlt-hub/react-slots", - "version": "2.0.0-next.0", + "version": "2.0.0", "license": "MIT", "devDependencies": { "@rslib/core": "0.17.0", @@ -18,7 +18,7 @@ "size-limit": "11.2.0", "tslib": "2.8.1", "typescript": "5.9.3", - "vitest": "4.0.6" + "vitest": "4.0.7" }, "peerDependencies": { "effector": "23", @@ -1414,16 +1414,16 @@ } }, "node_modules/@vitest/expect": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.0.6.tgz", - "integrity": "sha512-5j8UUlBVhOjhj4lR2Nt9sEV8b4WtbcYh8vnfhTNA2Kn5+smtevzjNq+xlBuVhnFGXiyPPNzGrOVvmyHWkS5QGg==", + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.0.7.tgz", + "integrity": "sha512-jGRG6HghnJDjljdjYIoVzX17S6uCVCBRFnsgdLGJ6CaxfPh8kzUKe/2n533y4O/aeZ/sIr7q7GbuEbeGDsWv4Q==", "dev": true, "license": "MIT", "dependencies": { "@standard-schema/spec": "^1.0.0", "@types/chai": "^5.2.2", - "@vitest/spy": "4.0.6", - "@vitest/utils": "4.0.6", + "@vitest/spy": "4.0.7", + "@vitest/utils": "4.0.7", "chai": "^6.0.1", "tinyrainbow": "^3.0.3" }, @@ -1432,13 +1432,13 @@ } }, "node_modules/@vitest/mocker": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.0.6.tgz", - "integrity": "sha512-3COEIew5HqdzBFEYN9+u0dT3i/NCwppLnO1HkjGfAP1Vs3vti1Hxm/MvcbC4DAn3Szo1M7M3otiAaT83jvqIjA==", + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.0.7.tgz", + "integrity": "sha512-OsDwLS7WnpuNslOV6bJkXVYVV/6RSc4eeVxV7h9wxQPNxnjRvTTrIikfwCbMyl8XJmW6oOccBj2Q07YwZtQcCw==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/spy": "4.0.6", + "@vitest/spy": "4.0.7", "estree-walker": "^3.0.3", "magic-string": "^0.30.19" }, @@ -1459,9 +1459,9 @@ } }, "node_modules/@vitest/pretty-format": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.0.6.tgz", - "integrity": "sha512-4vptgNkLIA1W1Nn5X4x8rLJBzPiJwnPc+awKtfBE5hNMVsoAl/JCCPPzNrbf+L4NKgklsis5Yp2gYa+XAS442g==", + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.0.7.tgz", + "integrity": "sha512-YY//yxqTmk29+/pK+Wi1UB4DUH3lSVgIm+M10rAJ74pOSMgT7rydMSc+vFuq9LjZLhFvVEXir8EcqMke3SVM6Q==", "dev": true, "license": "MIT", "dependencies": { @@ -1472,13 +1472,13 @@ } }, "node_modules/@vitest/runner": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.0.6.tgz", - "integrity": "sha512-trPk5qpd7Jj+AiLZbV/e+KiiaGXZ8ECsRxtnPnCrJr9OW2mLB72Cb824IXgxVz/mVU3Aj4VebY+tDTPn++j1Og==", + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.0.7.tgz", + "integrity": "sha512-orU1lsu4PxLEcDWfjVCNGIedOSF/YtZ+XMrd1PZb90E68khWCNzD8y1dtxtgd0hyBIQk8XggteKN/38VQLvzuw==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/utils": "4.0.6", + "@vitest/utils": "4.0.7", "pathe": "^2.0.3" }, "funding": { @@ -1486,13 +1486,13 @@ } }, "node_modules/@vitest/snapshot": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.0.6.tgz", - "integrity": "sha512-PaYLt7n2YzuvxhulDDu6c9EosiRuIE+FI2ECKs6yvHyhoga+2TBWI8dwBjs+IeuQaMtZTfioa9tj3uZb7nev1g==", + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.0.7.tgz", + "integrity": "sha512-xJL+Nkw0OjaUXXQf13B8iKK5pI9QVtN9uOtzNHYuG/o/B7fIEg0DQ+xOe0/RcqwDEI15rud1k7y5xznBKGUXAA==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/pretty-format": "4.0.6", + "@vitest/pretty-format": "4.0.7", "magic-string": "^0.30.19", "pathe": "^2.0.3" }, @@ -1501,9 +1501,9 @@ } }, "node_modules/@vitest/spy": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.0.6.tgz", - "integrity": "sha512-g9jTUYPV1LtRPRCQfhbMintW7BTQz1n6WXYQYRQ25qkyffA4bjVXjkROokZnv7t07OqfaFKw1lPzqKGk1hmNuQ==", + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.0.7.tgz", + "integrity": "sha512-FW4X8hzIEn4z+HublB4hBF/FhCVaXfIHm8sUfvlznrcy1MQG7VooBgZPMtVCGZtHi0yl3KESaXTqsKh16d8cFg==", "dev": true, "license": "MIT", "funding": { @@ -1511,13 +1511,13 @@ } }, "node_modules/@vitest/utils": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.0.6.tgz", - "integrity": "sha512-bG43VS3iYKrMIZXBo+y8Pti0O7uNju3KvNn6DrQWhQQKcLavMB+0NZfO1/QBAEbq0MaQ3QjNsnnXlGQvsh0Z6A==", + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.0.7.tgz", + "integrity": "sha512-HNrg9CM/Z4ZWB6RuExhuC6FPmLipiShKVMnT9JlQvfhwR47JatWLChA6mtZqVHqypE6p/z6ofcjbyWpM7YLxPQ==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/pretty-format": "4.0.6", + "@vitest/pretty-format": "4.0.7", "tinyrainbow": "^3.0.3" }, "funding": { @@ -2249,19 +2249,19 @@ } }, "node_modules/vitest": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.0.6.tgz", - "integrity": "sha512-gR7INfiVRwnEOkCk47faros/9McCZMp5LM+OMNWGLaDBSvJxIzwjgNFufkuePBNaesGRnLmNfW+ddbUJRZn0nQ==", + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.0.7.tgz", + "integrity": "sha512-xQroKAadK503CrmbzCISvQUjeuvEZzv6U0wlnlVFOi5i3gnzfH4onyQ29f3lzpe0FresAiTAd3aqK0Bi/jLI8w==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/expect": "4.0.6", - "@vitest/mocker": "4.0.6", - "@vitest/pretty-format": "4.0.6", - "@vitest/runner": "4.0.6", - "@vitest/snapshot": "4.0.6", - "@vitest/spy": "4.0.6", - "@vitest/utils": "4.0.6", + "@vitest/expect": "4.0.7", + "@vitest/mocker": "4.0.7", + "@vitest/pretty-format": "4.0.7", + "@vitest/runner": "4.0.7", + "@vitest/snapshot": "4.0.7", + "@vitest/spy": "4.0.7", + "@vitest/utils": "4.0.7", "debug": "^4.4.3", "es-module-lexer": "^1.7.0", "expect-type": "^1.2.2", @@ -2289,10 +2289,10 @@ "@edge-runtime/vm": "*", "@types/debug": "^4.1.12", "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0", - "@vitest/browser-playwright": "4.0.6", - "@vitest/browser-preview": "4.0.6", - "@vitest/browser-webdriverio": "4.0.6", - "@vitest/ui": "4.0.6", + "@vitest/browser-playwright": "4.0.7", + "@vitest/browser-preview": "4.0.7", + "@vitest/browser-webdriverio": "4.0.7", + "@vitest/ui": "4.0.7", "happy-dom": "*", "jsdom": "*" }, diff --git a/package.json b/package.json index 74a2390..5eb9172 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@grlt-hub/react-slots", - "version": "2.0.0-next.0", + "version": "2.0.0", "type": "module", "private": false, "main": "dist/index.js", @@ -19,7 +19,7 @@ "size-limit": "11.2.0", "tslib": "2.8.1", "typescript": "5.9.3", - "vitest": "4.0.6" + "vitest": "4.0.7" }, "peerDependencies": { "effector": "23",