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/CHANGELOG.md b/CHANGELOG.md
index ab4349f..e8480e7 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -3,6 +3,28 @@
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
+
+```tsx
+const userLoaded = createEvent<{ id: number }>();
+
+slotsApi.insert.into.Header({
+ when: userLoaded, // Wait for event
+ mapProps: (slotProps, whenPayload) => ({ userId: whenPayload.id }),
+ component: (props) =>
+
+
World`.
+// plugin-analytics/index.ts - inject from anywhere!
+import { slotsApi } from './Sidebar';
-### Result
+slotsApi.insert.into.Widgets({
+ component: () => Β© 1955β1985β2015 Outatime Corp.
, +}); -#### Step 2: Use the Slot in Your Component +// Result: +//Β© 1955β1985β2015 Outatime Corp.
+//{doubleText}
, +const { slotsApi, Slots } = createSlots({ + UserPanel: createSlotIdentifier<{ userId: number }>(), +} as const); + +First Component
, +// This is inserted first, but will render second +slotsApi.insert.into.Sidebar({ + component: () =>Second Component
, +// This is inserted second, but will render first +slotsApi.insert.into.Sidebar({ + component: () =>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 - +// Result: +// <> +//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) =>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 - +// 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/logo.webp b/logo.webp new file mode 100644 index 0000000..e988259 Binary files /dev/null and b/logo.webp differ diff --git a/package-lock.json b/package-lock.json index f63d78d..1c6272c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,26 +1,26 @@ { "name": "@grlt-hub/react-slots", - "version": "1.1.0", + "version": "2.0.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@grlt-hub/react-slots", - "version": "1.1.0", + "version": "2.0.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", "size-limit": "11.2.0", "tslib": "2.8.1", "typescript": "5.9.3", - "vitest": "4.0.6" + "vitest": "4.0.7" }, "peerDependencies": { - "@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "effector": "23", "effector-react": "23", "nanoid": "*", @@ -1407,23 +1407,23 @@ "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" } }, "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": { @@ -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", @@ -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 e6c8256..5eb9172 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@grlt-hub/react-slots", - "version": "1.1.0", + "version": "2.0.0", "type": "module", "private": false, "main": "dist/index.js", @@ -12,16 +12,16 @@ "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", "size-limit": "11.2.0", "tslib": "2.8.1", "typescript": "5.9.3", - "vitest": "4.0.6" + "vitest": "4.0.7" }, "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" ] } diff --git a/sandbox.tsx b/sandbox.tsx new file mode 100644 index 0000000..c7c99a8 --- /dev/null +++ b/sandbox.tsx @@ -0,0 +1,21 @@ +import { createEvent } 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 appGate = createGateHello world! {props.id}
, +}); + +slotsApi.insert.into.ConfirmScreenBottom({ + 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 8208f03..083f052 100644 --- a/src/__tests__/payload.spec-d.tsx +++ b/src/__tests__/payload.spec-d.tsx @@ -1,17 +1,20 @@ +import { createEvent } from 'effector'; import React from 'react'; import { expectTypeOf } from 'vitest'; -import { createSlotIdentifier, createSlots, type EmptyObject } from '../init'; +import { EmptyObject } from '../helpers'; +import { createSlotIdentifier, createSlots } from '../index'; -const slotId = createSlotIdentifier<{ text: string }>(); +const slotWithProps = createSlotIdentifier<{ text: string }>(); const noPropsSlot = createSlotIdentifier = Parameters = Parameters