@meonode/ui 0.1.18 → 0.1.19
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +248 -107
- package/dist/core.node.d.ts +4 -4
- package/dist/core.node.d.ts.map +1 -1
- package/dist/core.node.js +8 -10
- package/dist/html.node.d.ts +135 -135
- package/dist/html.node.d.ts.map +1 -1
- package/dist/html.node.js +1 -1
- package/dist/node.type.d.ts +11 -8
- package/dist/node.type.d.ts.map +1 -1
- package/package.json +5 -2
package/README.md
CHANGED
|
@@ -161,98 +161,268 @@ const Dashboard = Component(() =>
|
|
|
161
161
|
);
|
|
162
162
|
```
|
|
163
163
|
|
|
164
|
+
### Material UI Integration
|
|
165
|
+
|
|
166
|
+
```shell
|
|
167
|
+
yarn add @meonode/mui @mui/material
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
```ts
|
|
171
|
+
import { Button, TextField } from '@meonode/mui';
|
|
172
|
+
|
|
173
|
+
const MuiLoginForm = Component(() =>
|
|
174
|
+
Div({
|
|
175
|
+
maxWidth: '400px',
|
|
176
|
+
margin: '0 auto',
|
|
177
|
+
children: [
|
|
178
|
+
TextField({
|
|
179
|
+
label: 'Email',
|
|
180
|
+
fullWidth: true,
|
|
181
|
+
margin: 'normal'
|
|
182
|
+
}),
|
|
183
|
+
TextField({
|
|
184
|
+
label: 'Password',
|
|
185
|
+
type: 'password',
|
|
186
|
+
fullWidth: true,
|
|
187
|
+
margin: 'normal'
|
|
188
|
+
}),
|
|
189
|
+
Button({
|
|
190
|
+
variant: 'contained',
|
|
191
|
+
color: 'primary',
|
|
192
|
+
children: 'Sign In'
|
|
193
|
+
})
|
|
194
|
+
]
|
|
195
|
+
})
|
|
196
|
+
);
|
|
197
|
+
```
|
|
198
|
+
|
|
164
199
|
### With Conditional Children That Contains Hook
|
|
165
200
|
|
|
166
201
|
```ts
|
|
167
202
|
'use client'
|
|
168
203
|
/**
|
|
169
|
-
* This file
|
|
170
|
-
*
|
|
171
|
-
*
|
|
172
|
-
* and
|
|
204
|
+
* This file showcases the integration of React hooks with @meonode/ui components
|
|
205
|
+
* for building declarative user interfaces. It demonstrates different rendering
|
|
206
|
+
* approaches, the use of Higher-Order Components (HOCs), and how theme context
|
|
207
|
+
* is managed and propagated within the @meonode/ui component tree.
|
|
173
208
|
*/
|
|
174
|
-
import { Component, Column, Row,
|
|
175
|
-
import { useState, useEffect } from 'react'
|
|
176
|
-
import { CssBaseline, TextField } from '@meonode/mui'
|
|
209
|
+
import { Component, Column, Row, P, Node, Button, Theme, Center, NodeInstance } from '@meonode/ui'
|
|
210
|
+
import { useState, useEffect, ReactElement, ReactNode } from 'react'
|
|
211
|
+
import { CssBaseline, FormControlLabel, TextField } from '@meonode/mui'
|
|
212
|
+
import { Switch as MUISwitch } from '@mui/material'
|
|
213
|
+
import { styled } from '@mui/material'
|
|
214
|
+
|
|
215
|
+
const MaterialUISwitch = styled(MUISwitch)(({ theme }) => ({
|
|
216
|
+
width: 62,
|
|
217
|
+
height: 34,
|
|
218
|
+
padding: 7,
|
|
219
|
+
'& .MuiSwitch-switchBase': {
|
|
220
|
+
margin: 1,
|
|
221
|
+
padding: 0,
|
|
222
|
+
transform: 'translateX(6px)',
|
|
223
|
+
'&.Mui-checked': {
|
|
224
|
+
color: '#fff',
|
|
225
|
+
transform: 'translateX(22px)',
|
|
226
|
+
'& .MuiSwitch-thumb:before': {
|
|
227
|
+
backgroundImage: `url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" height="20" width="20" viewBox="0 0 20 20"><path fill="${encodeURIComponent(
|
|
228
|
+
'#fff',
|
|
229
|
+
)}" d="M4.2 2.5l-.7 1.8-1.8.7 1.8.7.7 1.8.6-1.8L6.7 5l-1.9-.7-.6-1.8zm15 8.3a6.7 6.7 0 11-6.6-6.6 5.8 5.8 0 006.6 6.6z"/></svg>')`,
|
|
230
|
+
},
|
|
231
|
+
'& + .MuiSwitch-track': {
|
|
232
|
+
opacity: 1,
|
|
233
|
+
backgroundColor: '#aab4be',
|
|
234
|
+
...theme.applyStyles('dark', {
|
|
235
|
+
backgroundColor: '#8796A5',
|
|
236
|
+
}),
|
|
237
|
+
},
|
|
238
|
+
},
|
|
239
|
+
},
|
|
240
|
+
'& .MuiSwitch-thumb': {
|
|
241
|
+
backgroundColor: '#001e3c',
|
|
242
|
+
width: 32,
|
|
243
|
+
height: 32,
|
|
244
|
+
'&::before': {
|
|
245
|
+
content: "''",
|
|
246
|
+
position: 'absolute',
|
|
247
|
+
width: '100%',
|
|
248
|
+
height: '100%',
|
|
249
|
+
left: 0,
|
|
250
|
+
top: 0,
|
|
251
|
+
backgroundRepeat: 'no-repeat',
|
|
252
|
+
backgroundPosition: 'center',
|
|
253
|
+
backgroundImage: `url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" height="20" width="20" viewBox="0 0 20 20"><path fill="${encodeURIComponent(
|
|
254
|
+
'#fff',
|
|
255
|
+
)}" d="M9.305 1.667V3.75h1.389V1.667h-1.39zm-4.707 1.95l-.982.982L5.09 6.072l.982-.982-1.473-1.473zm10.802 0L13.927 5.09l.982.982 1.473-1.473-.982-.982zM10 5.139a4.872 4.872 0 00-4.862 4.86A4.872 4.872 0 0010 14.862 4.872 4.872 0 0014.86 10 4.872 4.872 0 0010 5.139zm0 1.389A3.462 3.462 0 0113.471 10a3.462 3.462 0 01-3.473 3.472A3.462 3.462 0 016.527 10 3.462 3.462 0 0110 6.528zM1.665 9.305v1.39h2.083v-1.39H1.666zm14.583 0v1.39h2.084v-1.39h-2.084zM5.09 13.928L3.616 15.4l.982.982 1.473-1.473-.982-.982zm9.82 0l-.982.982 1.473 1.473.982-.982-1.473-1.473zM9.305 16.25v2.083h1.389V16.25h-1.39z"/></svg>')`,
|
|
256
|
+
},
|
|
257
|
+
...theme.applyStyles('dark', {
|
|
258
|
+
backgroundColor: '#003892',
|
|
259
|
+
}),
|
|
260
|
+
},
|
|
261
|
+
'& .MuiSwitch-track': {
|
|
262
|
+
opacity: 1,
|
|
263
|
+
backgroundColor: '#aab4be',
|
|
264
|
+
borderRadius: 20 / 2,
|
|
265
|
+
...theme.applyStyles('dark', {
|
|
266
|
+
backgroundColor: '#8796A5',
|
|
267
|
+
}),
|
|
268
|
+
},
|
|
269
|
+
}))
|
|
177
270
|
|
|
178
271
|
/**
|
|
179
|
-
*
|
|
180
|
-
*
|
|
181
|
-
*
|
|
182
|
-
*
|
|
272
|
+
* Defines the color palette for the light theme.
|
|
273
|
+
* These color values are used by @meonode/ui components when they encounter
|
|
274
|
+
* theme string references (e.g., 'theme.primary') and the current theme mode is 'light'.
|
|
275
|
+
* In a larger application, this theme object would typically reside in a dedicated theme file.
|
|
183
276
|
*/
|
|
184
|
-
const
|
|
185
|
-
|
|
277
|
+
const lightTheme: Theme = {
|
|
278
|
+
mode: 'light',
|
|
279
|
+
colors: {
|
|
280
|
+
primary: '#2563eb',
|
|
281
|
+
secondary: '#64748b',
|
|
282
|
+
accent: '#10b981',
|
|
283
|
+
background: '#ffffff',
|
|
284
|
+
foreground: '#0f172a',
|
|
285
|
+
border: '#e2e8f0',
|
|
286
|
+
muted: '#f8fafc',
|
|
287
|
+
success: '#16a34a',
|
|
288
|
+
warning: '#eab308',
|
|
289
|
+
danger: '#dc2626',
|
|
290
|
+
},
|
|
186
291
|
}
|
|
187
292
|
|
|
188
293
|
/**
|
|
189
|
-
*
|
|
190
|
-
*
|
|
191
|
-
*
|
|
192
|
-
*
|
|
193
|
-
|
|
294
|
+
* Defines the color palette for the dark theme.
|
|
295
|
+
* Similar to the light theme, these colors are used by @meonode/ui components
|
|
296
|
+
* when resolving theme string references, but specifically when the current theme
|
|
297
|
+
* mode is 'dark'.
|
|
298
|
+
*/
|
|
299
|
+
const darkTheme: Theme = {
|
|
300
|
+
mode: 'dark',
|
|
301
|
+
colors: {
|
|
302
|
+
primary: '#3b82f6',
|
|
303
|
+
secondary: '#94a3b8',
|
|
304
|
+
accent: '#34d399',
|
|
305
|
+
background: '#0f172a',
|
|
306
|
+
foreground: '#f8fafc',
|
|
307
|
+
border: '#334155',
|
|
308
|
+
muted: '#1e293b',
|
|
309
|
+
success: '#22c55e',
|
|
310
|
+
warning: '#facc15',
|
|
311
|
+
danger: '#ef4444',
|
|
312
|
+
},
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
/**
|
|
316
|
+
* The main page component, implemented as a functional component using React hooks.
|
|
317
|
+
* It manages the theme mode state and the visibility of additional content.
|
|
318
|
+
*
|
|
319
|
+
* This function is wrapped by the `Component` HOC from `@meonode/ui`. The `Component`
|
|
320
|
+
* HOC transforms the function into a standard React component that returns a `ReactNode`,
|
|
321
|
+
* making it compatible with React's rendering lifecycle and enabling SSR/CSR.
|
|
194
322
|
*/
|
|
195
323
|
export default Component(() => {
|
|
196
324
|
// State hook to control the visibility of additional content sections.
|
|
197
325
|
const [showMore, setShowDetails] = useState(false) // 'showMore' controls visibility, 'setShowDetails' is the updater.
|
|
326
|
+
const [mode, setMode] = useState<'dark' | 'light'>('light')
|
|
327
|
+
const theme = mode === 'dark' ? darkTheme : lightTheme
|
|
198
328
|
|
|
199
329
|
/**
|
|
200
|
-
* The
|
|
201
|
-
*
|
|
202
|
-
*
|
|
203
|
-
* - A
|
|
330
|
+
* The root of the UI tree is a `Column` component from `@meonode/ui`.
|
|
331
|
+
* This `Column` sets the theme context for its children.
|
|
332
|
+
* Its children include:
|
|
333
|
+
* - A theme toggle switch using MUI components wrapped in `@meonode/ui`'s `Node`.
|
|
334
|
+
* - A button to toggle the visibility of the detail sections.
|
|
335
|
+
* - Various examples demonstrating how to render components that return either
|
|
336
|
+
* `@meonode/ui` `Node` instances or `ReactNode`s, illustrating theme propagation
|
|
204
337
|
* both unconditionally and conditionally, highlighting theme propagation.
|
|
205
338
|
*/
|
|
206
339
|
return Column({
|
|
207
|
-
theme
|
|
340
|
+
theme: theme.colors,
|
|
208
341
|
padding: 20,
|
|
209
342
|
gap: 15,
|
|
343
|
+
minHeight: '100vh',
|
|
344
|
+
backgroundColor: 'theme.background',
|
|
345
|
+
color: 'theme.foreground',
|
|
210
346
|
children: [
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
347
|
+
// Theme toggle switch using MUI components wrapped with @meonode/ui's Node HOC.
|
|
348
|
+
Center({
|
|
349
|
+
children: FormControlLabel({
|
|
350
|
+
control: Node(MaterialUISwitch).render() as ReactElement,
|
|
351
|
+
alignItems: 'center',
|
|
352
|
+
label: mode === 'dark' ? 'Dark Mode' : 'Light Mode',
|
|
353
|
+
labelPlacement: 'start',
|
|
354
|
+
checked: mode === 'dark',
|
|
355
|
+
onChange: () => setMode(prev => (prev === 'dark' ? 'light' : 'dark')),
|
|
356
|
+
}),
|
|
357
|
+
}),
|
|
358
|
+
// Button to toggle the visibility of the detail sections.
|
|
359
|
+
Button(showMore ? 'Hide' : 'Show More', {
|
|
360
|
+
onClick: () => setShowDetails(prev => !prev), // Click handler to toggle the 'showMore' state.
|
|
361
|
+
cursor: 'pointer', // Visual cue for clickability.
|
|
362
|
+
userSelect: 'none', // Prevents text selection on the button.
|
|
363
|
+
padding: '10px 20px',
|
|
364
|
+
backgroundColor: 'theme.accent', // Background color sourced from the theme context.
|
|
365
|
+
borderRadius: 5,
|
|
366
|
+
fontWeight: 'bold',
|
|
367
|
+
color: 'white',
|
|
227
368
|
}),
|
|
369
|
+
CssBaseline, // Applies baseline Material UI styles for consistent rendering.
|
|
228
370
|
|
|
229
371
|
/**
|
|
230
|
-
* Unconditional
|
|
231
|
-
*
|
|
232
|
-
*
|
|
233
|
-
*
|
|
372
|
+
* --- Unconditional Rendering Examples ---
|
|
373
|
+
* These examples demonstrate rendering components that return either a
|
|
374
|
+
* `@meonode/ui` `Node` instance (`DetailComponent`) or a `ReactNode`
|
|
375
|
+
* (`ReturnRenderedDetailComponent`), and how the `Node` HOC affects this.
|
|
376
|
+
* Observe how theme context is propagated (or not) in each case.
|
|
234
377
|
*/
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
378
|
+
// 1. Rendering a component that returns a @meonode/ui Node instance directly.
|
|
379
|
+
// The internal Row component correctly receives theme context from the parent Column.
|
|
380
|
+
DetailComponent({ info: 'Detail 1 (Node instance)' }),
|
|
381
|
+
|
|
382
|
+
// 2. Rendering a component that returns a @meonode/ui Node instance, then calling .render().
|
|
383
|
+
// The internal Row component also correctly receives theme context.
|
|
384
|
+
DetailComponent({ info: 'Detail 2 (Node instance + .render())' }).render(),
|
|
385
|
+
|
|
386
|
+
// 3. Attempting to wrap a component returning a Node instance with Node HOC.
|
|
387
|
+
// ❌ Fails: The Node HOC expects the wrapped function to return a ReactNode, not a @meonode/ui Node instance.
|
|
388
|
+
// Node(DetailComponent, { info: 'Detail 3 (Node HOC on Node instance)' }),
|
|
389
|
+
|
|
390
|
+
// 4. Rendering a component that explicitly returns a ReactNode (.render() is called internally).
|
|
391
|
+
// The internal Row component correctly receives theme context from the parent Column.
|
|
392
|
+
ReturnRenderedDetailComponent({ info: 'Detail 4 (ReactNode)' }),
|
|
393
|
+
|
|
394
|
+
// 5. Wrapping a component returning ReactNode with Node HOC.
|
|
395
|
+
// Renders successfully. However, the Node HOC does NOT propagate theme context to the wrapped component's children.
|
|
396
|
+
Node(ReturnRenderedDetailComponent, { info: 'Detail 5 (Node HOC on ReactNode)' }),
|
|
238
397
|
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
Node(ReturnRenderedDetailComponent, { info: '
|
|
242
|
-
// Node(DetailComponent, { info: 'Here are some details 7!' }).render(), // ❌ Fails: Same reason as above; Node HOC expects a function returning ReactNode.
|
|
398
|
+
// 6. Wrapping a component returning ReactNode with Node HOC, then calling .render().
|
|
399
|
+
// Renders successfully. Theme context is NOT propagated by the Node HOC.
|
|
400
|
+
Node(ReturnRenderedDetailComponent, { info: 'Detail 6 (Node HOC on ReactNode + .render())' }).render(),
|
|
243
401
|
|
|
244
402
|
/**
|
|
245
403
|
* Conditional rendering examples (shown when 'showMore' is true):
|
|
246
404
|
* These demonstrate various wrapping techniques (inline functions, Component HOC)
|
|
247
405
|
* and their effect on rendering and theme propagation for both types of detail components.
|
|
248
406
|
*/
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
showMore &&
|
|
407
|
+
// 7. Conditional rendering of a component returning a Node instance using an inline function wrapper.
|
|
408
|
+
// Renders successfully when `showMore` is true. The internal Row receives theme context.
|
|
409
|
+
showMore && (() => DetailComponent({ info: 'Detail 7 (Conditional inline function + Node instance)' })),
|
|
252
410
|
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
showMore &&
|
|
411
|
+
// 8. Conditional rendering of a component returning a Node instance using an inline function wrapper, then calling .render().
|
|
412
|
+
// Renders successfully when `showMore` is true. The internal Row receives theme context.
|
|
413
|
+
showMore && (() => DetailComponent({ info: 'Detail 8 (Conditional inline function + Node instance + .render())' }).render()),
|
|
414
|
+
|
|
415
|
+
// 9. Conditional rendering of a component returning a Node instance using the Component HOC wrapper.
|
|
416
|
+
// Renders successfully when `showMore` is true. The internal Row receives theme context.
|
|
417
|
+
showMore && Component(() => DetailComponent({ info: 'Detail 9 (Conditional Component HOC + Node instance)' })),
|
|
418
|
+
|
|
419
|
+
// 10. Conditional rendering of a component returning ReactNode using an inline function wrapper.
|
|
420
|
+
// Renders successfully when `showMore` is true. The internal Row receives theme context.
|
|
421
|
+
showMore && (() => ReturnRenderedDetailComponent({ info: 'Detail 10 (Conditional inline function + ReactNode)' })),
|
|
422
|
+
|
|
423
|
+
// 11. Conditional rendering of a component returning ReactNode using the Component HOC wrapper.
|
|
424
|
+
// Renders successfully when `showMore` is true. The internal Row receives theme context.
|
|
425
|
+
showMore && Component(() => ReturnRenderedDetailComponent({ info: 'Detail 11 (Conditional Component HOC + ReactNode)' })),
|
|
256
426
|
// showMore && ReturnRenderedDetailComponent({ info: 'Here are some details 15!' }), // ❌ Fails: Direct call to a component function using hooks (ReturnRenderedDetailComponent) inside render logic without a React-aware wrapper. This can violate Rules of Hooks.
|
|
257
427
|
],
|
|
258
428
|
})
|
|
@@ -261,15 +431,17 @@ export default Component(() => {
|
|
|
261
431
|
/**
|
|
262
432
|
* A component that displays a styled detail section.
|
|
263
433
|
* It uses `useEffect` for lifecycle logging. The internal `Row` component
|
|
264
|
-
* sources its theme from the React context
|
|
265
|
-
*
|
|
266
|
-
* This component returns a @meonode/ui `Row` Node instance.
|
|
434
|
+
* sources its theme from the React context provided by an ancestor `@meonode/ui`
|
|
435
|
+
* component (like the main `Column` in this page).
|
|
267
436
|
*
|
|
437
|
+
* This component returns a @meonode/ui `Row` Node instance.
|
|
438
|
+
* This type of component is suitable for direct inclusion as a child within other
|
|
439
|
+
* `@meonode/ui` components that expect `Node` instances or arrays of `Node` instances.
|
|
268
440
|
* @param {object} props - Component properties.
|
|
269
441
|
* @param {string} props.info - Text content to display in the detail section.
|
|
270
|
-
* @returns {
|
|
442
|
+
* @returns {NodeInstance} A @meonode/ui Row Node instance.
|
|
271
443
|
*/
|
|
272
|
-
const DetailComponent = ({ info }: { info: string }) => {
|
|
444
|
+
const DetailComponent = ({ info }: { info: string }): NodeInstance => {
|
|
273
445
|
// useEffect hook for logging component mount and unmount phases (for debugging).
|
|
274
446
|
useEffect(() => {
|
|
275
447
|
console.log('DetailComponent mounted:', info) // Example mount log
|
|
@@ -278,31 +450,34 @@ const DetailComponent = ({ info }: { info: string }) => {
|
|
|
278
450
|
}
|
|
279
451
|
}, [info]) // Effect depends on 'info' prop.
|
|
280
452
|
|
|
281
|
-
// Returns a @meonode/ui Row
|
|
453
|
+
// Returns a @meonode/ui Row Node instance configured with props and children.
|
|
282
454
|
// Its styling (e.g., backgroundColor) will resolve theme strings from React context.
|
|
283
455
|
return Row({
|
|
456
|
+
alignItems: 'center',
|
|
284
457
|
gap: 10,
|
|
285
458
|
padding: 4,
|
|
286
|
-
border: '
|
|
459
|
+
border: '2px solid theme.accent',
|
|
287
460
|
borderRadius: 6,
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
children: [P(info), TextField({ background: 'theme.
|
|
461
|
+
backgroundColor: 'theme.warning', // Background color sourced from theme in React context.
|
|
462
|
+
color: 'theme.danger',
|
|
463
|
+
children: [P(info, { flex: 1, padding: '0 20px' }), TextField({ flex: 1, sx: { background: 'theme.primary' } })],
|
|
291
464
|
})
|
|
292
465
|
}
|
|
293
466
|
|
|
294
467
|
/**
|
|
295
468
|
* An alternative detail component implementation that explicitly calls `.render()`
|
|
296
469
|
* to return a `ReactNode` (a rendered React element) directly.
|
|
297
|
-
* This makes it compatible with
|
|
470
|
+
* This makes it compatible with standard React rendering patterns and wrappers
|
|
471
|
+
* like the `Node` HOC that specifically expect a function returning `ReactNode`.
|
|
298
472
|
* It uses `useEffect` for lifecycle logging. The internal `Row` sources its
|
|
299
473
|
* theme from React context.
|
|
300
474
|
*
|
|
475
|
+
* This component returns a `ReactNode`.
|
|
301
476
|
* @param {object} props - Component properties.
|
|
302
477
|
* @param {string} props.info - Text content to display.
|
|
303
478
|
* @returns {React.ReactNode} A rendered React element (the result of `Row(...).render()`).
|
|
304
479
|
*/
|
|
305
|
-
const ReturnRenderedDetailComponent = ({ info }: { info: string }) => {
|
|
480
|
+
const ReturnRenderedDetailComponent = ({ info }: { info: string }): ReactNode => {
|
|
306
481
|
// useEffect hook for logging component mount and unmount phases (for debugging).
|
|
307
482
|
useEffect(() => {
|
|
308
483
|
console.log('ReturnRenderedDetailComponent mounted:', info) // Example mount log
|
|
@@ -315,52 +490,18 @@ const ReturnRenderedDetailComponent = ({ info }: { info: string }) => {
|
|
|
315
490
|
// The Row itself will attempt to resolve theme strings (e.g., 'theme.background.secondary')
|
|
316
491
|
// from the React context provided by an ancestor @meonode/ui component (like the main Column).
|
|
317
492
|
return Row({
|
|
493
|
+
alignItems: 'center',
|
|
318
494
|
gap: 10,
|
|
319
495
|
padding: 4,
|
|
320
|
-
border: '
|
|
496
|
+
border: '2px solid theme.accent',
|
|
321
497
|
borderRadius: 6,
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
children: [P(info), TextField({ background: 'theme.
|
|
498
|
+
backgroundColor: 'theme.warning', // Theme-aware background; relies on theme from React context.
|
|
499
|
+
color: 'theme.danger',
|
|
500
|
+
children: [P(info, { flex: 1, padding: '0 20px' }), TextField({ flex: 1, sx: { background: 'theme.primary' } })],
|
|
325
501
|
}).render() // Explicitly renders to ReactNode.
|
|
326
502
|
}
|
|
327
503
|
```
|
|
328
504
|
|
|
329
|
-
### Material UI Integration
|
|
330
|
-
|
|
331
|
-
```shell
|
|
332
|
-
yarn add @meonode/mui @mui/material
|
|
333
|
-
```
|
|
334
|
-
|
|
335
|
-
```ts
|
|
336
|
-
import { Button, TextField } from '@meonode/mui';
|
|
337
|
-
|
|
338
|
-
const MuiLoginForm = Component(() =>
|
|
339
|
-
Div({
|
|
340
|
-
maxWidth: '400px',
|
|
341
|
-
margin: '0 auto',
|
|
342
|
-
children: [
|
|
343
|
-
TextField({
|
|
344
|
-
label: 'Email',
|
|
345
|
-
fullWidth: true,
|
|
346
|
-
margin: 'normal'
|
|
347
|
-
}),
|
|
348
|
-
TextField({
|
|
349
|
-
label: 'Password',
|
|
350
|
-
type: 'password',
|
|
351
|
-
fullWidth: true,
|
|
352
|
-
margin: 'normal'
|
|
353
|
-
}),
|
|
354
|
-
Button({
|
|
355
|
-
variant: 'contained',
|
|
356
|
-
color: 'primary',
|
|
357
|
-
children: 'Sign In'
|
|
358
|
-
})
|
|
359
|
-
]
|
|
360
|
-
})
|
|
361
|
-
);
|
|
362
|
-
```
|
|
363
|
-
|
|
364
505
|
## API Reference
|
|
365
506
|
|
|
366
507
|
### Core Functions
|
package/dist/core.node.d.ts
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import React, { type ReactNode } from 'react';
|
|
2
|
-
import type {
|
|
2
|
+
import type { ComponentNode, NodeElement, NodeInstance, NodeProps, Theme } from './node.type.js';
|
|
3
3
|
/**
|
|
4
4
|
* Factory function to create a BaseNode instance.
|
|
5
5
|
* @param element The React element type.
|
|
6
6
|
* @param props The props for the node.
|
|
7
|
-
* @returns
|
|
7
|
+
* @returns NodeInstance<E> - A new BaseNode instance.
|
|
8
8
|
*/
|
|
9
|
-
export declare function Node<E extends NodeElement>(element: E, props?: Partial<NodeProps<E>>):
|
|
9
|
+
export declare function Node<E extends NodeElement>(element: E, props?: Partial<NodeProps<E>>): NodeInstance<E>;
|
|
10
10
|
/**
|
|
11
11
|
* Higher-order component wrapper that converts BaseNode components into React components.
|
|
12
12
|
* This wrapper ensures proper theme propagation and component rendering in the React ecosystem.
|
|
@@ -31,5 +31,5 @@ export declare function Node<E extends NodeElement>(element: E, props?: Partial<
|
|
|
31
31
|
*/
|
|
32
32
|
export declare function Component<T extends Record<string, any> & {
|
|
33
33
|
theme?: Theme;
|
|
34
|
-
}>(component: (props: T) => ComponentNode): (props?:
|
|
34
|
+
}>(component: (props: T) => ComponentNode): (props?: any) => string | number | bigint | boolean | React.ReactElement<unknown, string | React.JSXElementConstructor<any>> | Iterable<React.ReactNode> | Promise<string | number | bigint | boolean | React.ReactPortal | React.ReactElement<unknown, string | React.JSXElementConstructor<any>> | Iterable<React.ReactNode> | null | undefined> | NodeInstance<any> | (() => NodeInstance<any> | ReactNode) | null | undefined;
|
|
35
35
|
//# sourceMappingURL=core.node.d.ts.map
|
package/dist/core.node.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"core.node.d.ts","sourceRoot":"","sources":["../src/core.node.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,EAAkF,KAAK,SAAS,EAAE,MAAM,OAAO,CAAA;AAC7H,OAAO,KAAK,EAAE,
|
|
1
|
+
{"version":3,"file":"core.node.d.ts","sourceRoot":"","sources":["../src/core.node.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,EAAkF,KAAK,SAAS,EAAE,MAAM,OAAO,CAAA;AAC7H,OAAO,KAAK,EAAE,aAAa,EAAyC,WAAW,EAAE,YAAY,EAAE,SAAS,EAAgB,KAAK,EAAE,MAAM,mBAAmB,CAAA;AA0bxJ;;;;;GAKG;AACH,wBAAgB,IAAI,CAAC,CAAC,SAAS,WAAW,EAAE,OAAO,EAAE,CAAC,EAAE,KAAK,GAAE,OAAO,CAAC,SAAS,CAAC,CAAC,CAAC,CAAM,GAAG,YAAY,CAAC,CAAC,CAAC,CAU1G;AAED;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAgB,SAAS,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG;IAAE,KAAK,CAAC,EAAE,KAAK,CAAA;CAAE,EAAE,SAAS,EAAE,CAAC,KAAK,EAAE,CAAC,KAAK,aAAa,IAEzG,QAAO,GAAQ,sZAmBxB"}
|
package/dist/core.node.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
"use strict";var _excluded=["children","nodeTheme","theme"],_excluded2=["children","key"];function _typeof(a){"@babel/helpers - typeof";return _typeof="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(a){return typeof a}:function(a){return a&&"function"==typeof Symbol&&a.constructor===Symbol&&a!==Symbol.prototype?"symbol":typeof a},_typeof(a)}function _objectWithoutProperties(a,b){if(null==a)return{};var c,d,e=_objectWithoutPropertiesLoose(a,b);if(Object.getOwnPropertySymbols){var f=Object.getOwnPropertySymbols(a);for(d=0;d<f.length;d++)c=f[d],-1===b.indexOf(c)&&{}.propertyIsEnumerable.call(a,c)&&(e[c]=a[c])}return e}function _objectWithoutPropertiesLoose(a,b){if(null==a)return{};var c={};for(var d in a)if({}.hasOwnProperty.call(a,d)){if(-1!==b.indexOf(d))continue;c[d]=a[d]}return c}function ownKeys(a,b){var c=Object.keys(a);if(Object.getOwnPropertySymbols){var d=Object.getOwnPropertySymbols(a);b&&(d=d.filter(function(b){return Object.getOwnPropertyDescriptor(a,b).enumerable})),c.push.apply(c,d)}return c}function _objectSpread(a){for(var b,c=1;c<arguments.length;c++)b=null==arguments[c]?{}:arguments[c],c%2?ownKeys(Object(b),!0).forEach(function(c){_defineProperty(a,c,b[c])}):Object.getOwnPropertyDescriptors?Object.defineProperties(a,Object.getOwnPropertyDescriptors(b)):ownKeys(Object(b)).forEach(function(c){Object.defineProperty(a,c,Object.getOwnPropertyDescriptor(b,c))});return a}function _classCallCheck(b,a){if(!(b instanceof a))throw new TypeError("Cannot call a class as a function")}function _defineProperties(a,b){for(var c,d=0;d<b.length;d++)c=b[d],c.enumerable=c.enumerable||!1,c.configurable=!0,"value"in c&&(c.writable=!0),Object.defineProperty(a,_toPropertyKey(c.key),c)}function _createClass(a,b,c){return b&&_defineProperties(a.prototype,b),c&&_defineProperties(a,c),Object.defineProperty(a,"prototype",{writable:!1}),a}function _defineProperty(a,b,c){return(b=_toPropertyKey(b))in a?Object.defineProperty(a,b,{value:c,enumerable:!0,configurable:!0,writable:!0}):a[b]=c,a}function _toPropertyKey(a){var b=_toPrimitive(a,"string");return"symbol"==_typeof(b)?b:b+""}function _toPrimitive(a,b){if("object"!=_typeof(a)||!a)return a;var c=a[Symbol.toPrimitive];if(void 0!==c){var d=c.call(a,b||"default");if("object"!=_typeof(d))return d;throw new TypeError("@@toPrimitive must return a primitive value.")}return("string"===b?String:Number)(a)}import React,{createElement,isValidElement}from"react";import{getComponentType,getCSSProps,getDOMProps,getElementTypeName,getValueByPath}from"./node.helper.js";import{isForwardRef,isMemo,isReactClassComponent,isValidElementType}from"./react-is.helper.js";/**
|
|
1
|
+
"use strict";var _excluded=["children","nodeTheme","theme"],_excluded2=["children","key"];function _typeof(a){"@babel/helpers - typeof";return _typeof="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(a){return typeof a}:function(a){return a&&"function"==typeof Symbol&&a.constructor===Symbol&&a!==Symbol.prototype?"symbol":typeof a},_typeof(a)}function _objectWithoutProperties(a,b){if(null==a)return{};var c,d,e=_objectWithoutPropertiesLoose(a,b);if(Object.getOwnPropertySymbols){var f=Object.getOwnPropertySymbols(a);for(d=0;d<f.length;d++)c=f[d],-1===b.indexOf(c)&&{}.propertyIsEnumerable.call(a,c)&&(e[c]=a[c])}return e}function _objectWithoutPropertiesLoose(a,b){if(null==a)return{};var c={};for(var d in a)if({}.hasOwnProperty.call(a,d)){if(-1!==b.indexOf(d))continue;c[d]=a[d]}return c}function ownKeys(a,b){var c=Object.keys(a);if(Object.getOwnPropertySymbols){var d=Object.getOwnPropertySymbols(a);b&&(d=d.filter(function(b){return Object.getOwnPropertyDescriptor(a,b).enumerable})),c.push.apply(c,d)}return c}function _objectSpread(a){for(var b,c=1;c<arguments.length;c++)b=null==arguments[c]?{}:arguments[c],c%2?ownKeys(Object(b),!0).forEach(function(c){_defineProperty(a,c,b[c])}):Object.getOwnPropertyDescriptors?Object.defineProperties(a,Object.getOwnPropertyDescriptors(b)):ownKeys(Object(b)).forEach(function(c){Object.defineProperty(a,c,Object.getOwnPropertyDescriptor(b,c))});return a}function _classCallCheck(b,a){if(!(b instanceof a))throw new TypeError("Cannot call a class as a function")}function _defineProperties(a,b){for(var c,d=0;d<b.length;d++)c=b[d],c.enumerable=c.enumerable||!1,c.configurable=!0,"value"in c&&(c.writable=!0),Object.defineProperty(a,_toPropertyKey(c.key),c)}function _createClass(a,b,c){return b&&_defineProperties(a.prototype,b),c&&_defineProperties(a,c),Object.defineProperty(a,"prototype",{writable:!1}),a}function _defineProperty(a,b,c){return(b=_toPropertyKey(b))in a?Object.defineProperty(a,b,{value:c,enumerable:!0,configurable:!0,writable:!0}):a[b]=c,a}function _toPropertyKey(a){var b=_toPrimitive(a,"string");return"symbol"==_typeof(b)?b:b+""}function _toPrimitive(a,b){if("object"!=_typeof(a)||!a)return a;var c=a[Symbol.toPrimitive];if(void 0!==c){var d=c.call(a,b||"default");if("object"!=_typeof(d))return d;throw new TypeError("@@toPrimitive must return a primitive value.")}return("string"===b?String:Number)(a)}import React,{createElement,isValidElement}from"react";import{getComponentType,getCSSProps,getDOMProps,getElementTypeName,getValueByPath}from"./node.helper.js";import{isForwardRef,isMemo,isReactClassComponent,isValidElementType}from"./react-is.helper.js";import{createRoot}from"react-dom/client";/**
|
|
2
2
|
* Represents a node in a React component tree with theme and styling capabilities.
|
|
3
3
|
* This class wraps React elements and handles:
|
|
4
4
|
* - Props processing and normalization
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* - Child node processing and management
|
|
7
7
|
* - Style processing with theme variables
|
|
8
8
|
* @template E The type of React element or component this node represents
|
|
9
|
-
*/var BaseNode=/*#__PURE__*/function(){/**
|
|
9
|
+
*/var BaseNode=/*#__PURE__*/function(){/**
|
|
10
10
|
* Creates a new BaseNode instance that wraps a React element.
|
|
11
11
|
* Processes raw props by:
|
|
12
12
|
* - Extracting and resolving theme-aware styles
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
* - Normalizing children with theme inheritance
|
|
15
15
|
* @param element The React element/component to wrap
|
|
16
16
|
* @param rawProps Initial props including theme, styles, and children
|
|
17
|
-
*/function BaseNode(a){var b=this,c=1<arguments.length&&arguments[1]!==void 0?arguments[1]:{};_classCallCheck(this,BaseNode),_defineProperty(this,"_normalizeChild",function(a){var c,d;if(!a)return a;var e=(null===(c=b.rawProps)||void 0===c?void 0:c.nodeTheme)||(null===(d=b.rawProps)||void 0===d?void 0:d.theme)||b.props.nodeTheme||b.props.theme;// For BaseNode instances, apply current theme if child has no theme
|
|
17
|
+
*/function BaseNode(a){var b=this,c=1<arguments.length&&arguments[1]!==void 0?arguments[1]:{};_classCallCheck(this,BaseNode),_defineProperty(this,"_portalDOMElement",null),_defineProperty(this,"_portalReactRoot",null),_defineProperty(this,"_normalizeChild",function(a){var c,d;if(!a)return a;var e=(null===(c=b.rawProps)||void 0===c?void 0:c.nodeTheme)||(null===(d=b.rawProps)||void 0===d?void 0:d.theme)||b.props.nodeTheme||b.props.theme;// For BaseNode instances, apply current theme if child has no theme
|
|
18
18
|
if(a instanceof BaseNode){var f;return null!==(f=a.rawProps)&&void 0!==f&&f.nodeTheme||void 0===e?a.render():new BaseNode(a.element,_objectSpread(_objectSpread({},a.rawProps),{},{nodeTheme:e})).render()}// For React.Component instances, wrap in BaseNode with theme if needed
|
|
19
19
|
if(a instanceof React.Component)return a.props.nodeTheme||void 0===e?a.render():new BaseNode(a.render(),_objectSpread(_objectSpread({},a.props),{},{nodeTheme:e})).render();// Validate element type before returning
|
|
20
20
|
if(!isValidElementType(a)){var g=getComponentType(a);throw new Error("Invalid element type: ".concat(g," provided!"))}// Return valid React elements as-is
|
|
@@ -33,13 +33,11 @@ d&&(Array.isArray(d)?k=d.map(function(a,c){return b._processRawNode(a,e,c)}):k=t
|
|
|
33
33
|
*/return _createClass(BaseNode,[{key:"_resolveObjWithTheme",value:function _resolveObjWithTheme(a,b){var c;// Return early if no theme or empty object
|
|
34
34
|
if(!b||0===Object.keys(a).length)return a;// Merge raw nodeTheme with passed theme for resolution
|
|
35
35
|
var d=_objectSpread(_objectSpread({},null===(c=this.rawProps)||void 0===c?void 0:c.nodeTheme),b),e=function resolveRecursively(a){var b={};// Process each property in the current object
|
|
36
|
-
for(var c in a)
|
|
37
|
-
if(Object.prototype.hasOwnProperty.call(a,c)){var f=a[c];// Skip private properties starting with underscore
|
|
38
|
-
if(c.startsWith("_"))return a;// Handle string values containing theme references
|
|
36
|
+
for(var c in a){var f=a[c];if(c.startsWith("_"))return a;// Handle string values containing theme references
|
|
39
37
|
if("string"==typeof f&&f.includes("theme.")){var g=f;// Replace theme path placeholders with actual theme values
|
|
40
|
-
g=g.replace(/theme\.([a-zA-Z0-9_.-]+)/g,function(a,b){var c=getValueByPath(d,b);// Convert theme value to string if it exists and is a valid type
|
|
38
|
+
g=g.replace(/theme\.([a-zA-Z0-9_.-]+)/g,function(a,b){var c=getValueByPath(d,b);// Convert the theme value to string if it exists and is a valid type
|
|
41
39
|
return null!=c&&["string","number"].includes(_typeof(c))?c+"":a}),b[c]=g}// Recursively process nested objects (excluding arrays)
|
|
42
|
-
else b[c]=f&&"object"===_typeof(f)
|
|
40
|
+
else b[c]=f&&"object"===_typeof(f)?e(f):f}return b};/**
|
|
43
41
|
* Recursively resolves theme values in an object
|
|
44
42
|
* @param currentObj The current object level being processed
|
|
45
43
|
* @returns New object with resolved theme values
|
|
@@ -125,11 +123,11 @@ var h=_objectSpread(_objectSpread({},e),{},{// Cast otherProps
|
|
|
125
123
|
key:d// This is the key of the current BaseNode itself
|
|
126
124
|
});// Prepare props for React.createElement
|
|
127
125
|
// Delete the key `nodeTheme` as it's not a valid DOM/React prop for the element
|
|
128
|
-
return delete h.nodeTheme,createElement(this.element,h,f)}}])}();/**
|
|
126
|
+
return delete h.nodeTheme,createElement(this.element,h,f)}},{key:"_ensurePortalInfrastructure",value:function _ensurePortalInfrastructure(){if("undefined"==typeof window)return!1;if(this._portalDOMElement&&this._portalReactRoot)return!0;if(this._portalDOMElement&&!this._portalDOMElement.isConnected&&(this._portalDOMElement=null,this._portalDOMElement=null),this._portalDOMElement||(this._portalDOMElement=document.createElement("div"),document.body.appendChild(this._portalDOMElement)),!this._portalReactRoot){if(!this._portalDOMElement)return!1;this._portalReactRoot=createRoot(this._portalDOMElement)}return!0}},{key:"toPortal",value:function toPortal(){var a=this;if(!this._ensurePortalInfrastructure()||!this._portalReactRoot)return null;var b=this.render();return this._portalReactRoot.render(b),_objectSpread(_objectSpread({},this._portalReactRoot),{},{unmount:function unmount(){a._portalReactRoot&&(a._portalReactRoot.unmount(),a._portalReactRoot=null),a._portalDOMElement&&(a._portalDOMElement.parentNode&&a._portalDOMElement.parentNode.removeChild(a._portalDOMElement),a._portalDOMElement=null)}})}}])}();/**
|
|
129
127
|
* Factory function to create a BaseNode instance.
|
|
130
128
|
* @param element The React element type.
|
|
131
129
|
* @param props The props for the node.
|
|
132
|
-
* @returns
|
|
130
|
+
* @returns NodeInstance<E> - A new BaseNode instance.
|
|
133
131
|
*/export function Node(a){var b=1<arguments.length&&void 0!==arguments[1]?arguments[1]:{},c=_objectSpread({},b);// Ensure we are working with a mutable copy
|
|
134
132
|
// 'theme' prop itself is not directly used by BaseNode after this, nodeTheme is used.
|
|
135
133
|
// We can keep `theme` in rawProps if needed for cloning or inspection.
|