Skip to content

Commit 2c5444f

Browse files
authored
Merge pull request #109 from atellmer/signals
Add Signals
2 parents f04372a + df22712 commit 2c5444f

File tree

97 files changed

+1731
-1784
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

97 files changed

+1731
-1784
lines changed

README.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ The lightweight and powerful UI rendering engine without dependencies and writte
3232
- 🎀 Suspense fallbacks
3333
- 🧲 Error boundaries
3434
- ☄️ Hot module replacement
35+
- 🔔 Signals
3536
- 💅 Styled components
3637
- 💃🏼 Spring animations
3738
- 💽 Server-side rendering
@@ -100,8 +101,8 @@ If you liked the project, please rate it with a star ⭐, it gives me inspiratio
100101

101102
## Ecosystem
102103

103-
| Package | Description | URL |
104-
|----------------------------------|------------------------------------------------------------------|--------------------------------------------------------------------------------|
104+
| Package | Description | URL |
105+
|----------------------------------|------------------------------------------------------------------|------------------------------------------|
105106
| `@dark-engine/core` | Abstract core with main functionality | [Link](https://github.com/atellmer/dark/tree/master/packages/core) |
106107
| `@dark-engine/platform-browser` | Renderer for browser (Single-Page apps) | [Link](https://github.com/atellmer/dark/tree/master/packages/platform-browser) |
107108
| `@dark-engine/platform-server` | Renderer for Node.js (Multi-Page, Static-Gen and Universal apps) | [Link](https://github.com/atellmer/dark/tree/master/packages/platform-server) |
@@ -112,6 +113,7 @@ If you liked the project, please rate it with a star ⭐, it gives me inspiratio
112113
| `@dark-engine/animations` | Spring based animations | [Link](https://github.com/atellmer/dark/tree/master/packages/animations) |
113114
| `@dark-engine/styled` | Styled components | [Link](https://github.com/atellmer/dark/tree/master/packages/styled) |
114115
| `@dark-engine/data` | Declarative queries and mutations | [Link](https://github.com/atellmer/dark/tree/master/packages/data) |
116+
| `@dark-engine/signals` | Signals, computed signals and signal effects | [Link](https://github.com/atellmer/dark/tree/master/packages/signals) |
115117

116118
## Usage
117119

examples/10k-rows/index.tsx

Lines changed: 27 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,6 @@
1-
import {
2-
type WritableAtom,
3-
Text,
4-
TagVirtualNode,
5-
TextVirtualNode,
6-
Flag,
7-
component,
8-
memo,
9-
useMemo,
10-
atom,
11-
} from '@dark-engine/core';
1+
import { Text, TagVirtualNode, TextVirtualNode, component, memo, useMemo } from '@dark-engine/core';
122
import { type SyntheticEvent as E, createRoot, table, tbody, div, button } from '@dark-engine/platform-browser';
3+
import { type Signal, signal, useSelected } from '@dark-engine/signals';
134

145
const createMeasurer = () => {
156
let startTime: number;
@@ -46,11 +37,11 @@ const buildData = (count, prefix = ''): Array<DataItem> => {
4637
.fill(0)
4738
.map(() => ({
4839
id: ++nextId,
49-
name$: atom(`item: ${nextId} ${prefix}`),
40+
name$: signal(`item: ${nextId} ${prefix}`),
5041
}));
5142
};
5243

53-
type DataItem = { id: number; name$: WritableAtom<string> };
44+
type DataItem = { id: number; name$: Signal<string> };
5445

5546
type HeaderProps = {
5647
onCreate: (e: E<MouseEvent>) => void;
@@ -104,25 +95,26 @@ const Header = component<HeaderProps>(
10495
const MemoHeader = memo(Header, () => false);
10596

10697
type NameProps = {
107-
name$: WritableAtom<string>;
98+
name$: Signal<string>;
10899
};
109100

110-
const Name = component<NameProps>(({ name$ }) => new TextVirtualNode(name$.val()));
101+
const Name = component<NameProps>(({ name$ }) => new TextVirtualNode(name$.get()));
111102

112103
type RowProps = {
113104
id: number;
114-
name$: WritableAtom<string>;
115-
selected$: WritableAtom<number>;
105+
name$: Signal<string>;
106+
selected$: Signal<number>;
116107
onRemove: (id: number, e: E<MouseEvent>) => void;
117108
onHighlight: (id: number, e: E<MouseEvent>) => void;
118109
};
119110

120-
const Row = component<RowProps>(({ id, selected$, name$, onRemove, onHighlight }) => {
111+
const Row = component<RowProps>(({ id, name$, selected$, onRemove, onHighlight }) => {
112+
const isSelected = useSelected(selected$, id) === id;
113+
121114
return new TagVirtualNode(
122115
'tr',
123116
{
124-
class: selected$.val(null, id) === id ? 'selected' : undefined,
125-
[Flag.STATIC_SLOT_OPT]: true,
117+
class: isSelected ? 'selected' : undefined,
126118
},
127119
[
128120
new TagVirtualNode('td', {}, [Name({ name$ })]),
@@ -138,15 +130,17 @@ const Row = component<RowProps>(({ id, selected$, name$, onRemove, onHighlight }
138130

139131
const MemoRow = memo(Row, () => false);
140132

133+
const equal = () => false;
134+
141135
type State = {
142-
data$: WritableAtom<Array<DataItem>>;
143-
selected$: WritableAtom<number>;
136+
data$: Signal<Array<DataItem>>;
137+
selected$: Signal<number>;
144138
};
145139

146140
const App = component(() => {
147-
const state = useMemo<State>(() => ({ data$: atom([]), selected$: atom() }), []);
141+
const state = useMemo<State>(() => ({ data$: signal([], { equal }), selected$: signal(undefined) }), []);
148142
const { data$, selected$ } = state;
149-
const items = data$.val();
143+
const data = data$.get();
150144

151145
const handleCreate = (e: E<MouseEvent>) => {
152146
measurer.start('create');
@@ -157,23 +151,23 @@ const App = component(() => {
157151
const handlePrepend = (e: E<MouseEvent>) => {
158152
measurer.start('prepend');
159153
e.stopPropagation();
160-
const data = data$.get();
154+
const data = data$.peek();
161155
data.unshift(...buildData(1000, '^^^'));
162156
data$.set(data);
163157
measurer.stop();
164158
};
165159
const handleAppend = (e: E<MouseEvent>) => {
166160
measurer.start('append');
167161
e.stopPropagation();
168-
const data = data$.get();
162+
const data = data$.peek();
169163
data.push(...buildData(1000, '^^^'));
170164
data$.set(data);
171165
measurer.stop();
172166
};
173167
const handleInsertDifferent = (e: E<MouseEvent>) => {
174168
measurer.start('insert different');
175169
e.stopPropagation();
176-
const data = data$.get();
170+
const data = data$.peek();
177171
data.splice(0, 0, ...buildData(5, '***'));
178172
data.splice(8, 0, ...buildData(2, '***'));
179173
data$.set(data);
@@ -182,17 +176,18 @@ const App = component(() => {
182176
const handleUpdateAll = (e: E<MouseEvent>) => {
183177
measurer.start('update every 10th');
184178
e.stopPropagation();
185-
const data = data$.get();
179+
const data = data$.peek();
186180

187181
for (let i = 0; i < data.length; i += 10) {
188182
data[i].name$.set(x => x + '!!!');
189183
}
184+
190185
measurer.stop();
191186
};
192187
const handleRemove = (id: number, e: E<MouseEvent>) => {
193188
measurer.start('remove');
194189
e.stopPropagation();
195-
const data = data$.get();
190+
const data = data$.peek();
196191
const idx = data.findIndex(x => x.id === id);
197192
idx !== -1 && data.splice(idx, 1);
198193
data$.set(data);
@@ -205,7 +200,7 @@ const App = component(() => {
205200
measurer.stop();
206201
};
207202
const handleSwap = (e: E<MouseEvent>) => {
208-
const data = data$.get();
203+
const data = data$.peek();
209204
if (data.length === 0) return;
210205
measurer.start('swap');
211206
e.stopPropagation();
@@ -235,9 +230,8 @@ const App = component(() => {
235230
table({
236231
class: 'table',
237232
slot: tbody({
238-
key: items.length > 0 ? 1 : 2,
239-
[Flag.MEMO_SLOT_OPT]: true,
240-
slot: items.map(item => {
233+
key: data.length > 0 ? 1 : 2,
234+
slot: data.map(item => {
241235
const { id, name$ } = item;
242236

243237
return MemoRow({

examples/1k-components/index.tsx

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { interpolateViridis } from 'd3-scale-chromatic';
22

3-
import { component, useState, useEffect, useMemo, useUpdate, Flag, TagVirtualNode } from '@dark-engine/core';
3+
import { component, useState, useEffect, useMemo, useUpdate, TagVirtualNode } from '@dark-engine/core';
44
import { createRoot } from '@dark-engine/platform-browser';
55

66
const Demo = component(() => {
@@ -38,8 +38,6 @@ const Layout = {
3838

3939
const LAYOUT_ORDER = [Layout.PHYLLOTAXIS, Layout.SPIRAL, Layout.PHYLLOTAXIS, Layout.GRID, Layout.WAVE];
4040

41-
const flagProps = { [Flag.SKIP_SCAN_OPT]: true };
42-
4341
type VizDemoProps = {
4442
count: number;
4543
};
@@ -106,8 +104,8 @@ const VizDemo = component<VizDemoProps>(({ count }) => {
106104
};
107105

108106
return (
109-
<svg class='demo' {...flagProps}>
110-
<g {...flagProps}>{map(scope.points, renderPoint)}</g>
107+
<svg class='demo'>
108+
<g>{map(scope.points, renderPoint)}</g>
111109
</svg>
112110
);
113111
});

examples/1k-components/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
"scripts": {
55
"start": "npm run dev",
66
"build": "npx webpack --env production",
7-
"dev": "npx webpack-dev-server --env development"
7+
"dev": "npx webpack-dev-server --env production"
88
},
99
"devDependencies": {
1010
"ts-loader": "9.4.2",

examples/atoms.ts

Lines changed: 0 additions & 74 deletions
This file was deleted.

examples/concurrent-router/index.tsx

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,7 @@ import { createRoot } from '@dark-engine/platform-browser';
33
import { type Routes, Router, NavLink } from '@dark-engine/web-router';
44
import { createGlobalStyle, styled } from '@dark-engine/styled';
55

6-
import { PageTransition } from '../spring-router/page-transition';
7-
import { Pending } from './pending';
6+
import { isPending$, Pending } from './pending';
87

98
const Home = lazy(() => import('./home'));
109
const About = lazy(() => import('./about'));
@@ -64,7 +63,7 @@ type ShellProps = {
6463
const Shell = component<ShellProps>(
6564
({ slot }) => {
6665
return (
67-
<PageTransition>
66+
<>
6867
<header>
6968
<NavLink to='/home'>Home</NavLink>
7069
<NavLink to='/about'>About</NavLink>
@@ -75,7 +74,7 @@ const Shell = component<ShellProps>(
7574
<Content>{slot}</Content>
7675
</Suspense>
7776
<SlowContent />
78-
</PageTransition>
77+
</>
7978
);
8079
},
8180
{ displayName: 'Shell' },
@@ -85,7 +84,7 @@ const App = component(() => {
8584
return (
8685
<>
8786
<GlobalStyle />
88-
<Router routes={routes} mode='concurrent'>
87+
<Router routes={routes} mode='concurrent' onChangePending={isPending => isPending$.set(isPending)}>
8988
{slot => <Shell>{slot}</Shell>}
9089
</Router>
9190
</>

examples/concurrent-router/pending.tsx

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
import { component } from '@dark-engine/core';
22
import { type DarkJSX } from '@dark-engine/platform-browser';
3-
import { usePending } from '@dark-engine/web-router';
43
import { styled } from '@dark-engine/styled';
4+
import { signal } from '@dark-engine/signals';
5+
6+
const isPending$ = signal(false);
57

68
const Pending = component(() => {
7-
const isPending = usePending();
9+
const isPending = isPending$.get();
810

911
return (
1012
<>
@@ -26,4 +28,4 @@ const Overlay = styled.div<{ isPending: boolean } & DarkJSX.Elements['div']>`
2628
background-color: #fff;
2729
`;
2830

29-
export { Pending };
31+
export { isPending$, Pending };

examples/nativescript/src/components/card.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { component, useAtom } from '@dark-engine/core';
1+
import { component, useState } from '@dark-engine/core';
22
import { View, Text } from '@dark-engine/platform-native';
33

44
import { Button } from './button';
@@ -8,7 +8,7 @@ type CardProps = {
88
};
99

1010
const Card = component<CardProps>(({ isLast }) => {
11-
const count$ = useAtom(0);
11+
const [count, setCount] = useState(0);
1212

1313
return (
1414
<View
@@ -23,9 +23,9 @@ const Card = component<CardProps>(({ isLast }) => {
2323
alignItems='center'
2424
marginBottom={isLast ? 66 : 8}>
2525
<Text fontSize={20} marginBottom={8}>
26-
Count is {count$.val()}
26+
Count is {count}
2727
</Text>
28-
<Button label='Press me' onPress={() => count$.set(x => x + 1)} />
28+
<Button label='Press me' onPress={() => setCount(x => x + 1)} />
2929
</View>
3030
);
3131
});

0 commit comments

Comments
 (0)