1+ import isPlainObject from "lodash.isplainobject" ;
2+ import isEqual from "lodash.isequal" ;
3+
4+ const resolveDescendants = ( children : any [ ] ) => {
5+ const nodes : any [ ] = [ ] ;
6+
7+ const addChild = ( child : any ) : void => {
8+ if ( child == null ) {
9+ return ;
10+ }
11+
12+ const prev = nodes [ nodes . length - 1 ] ;
13+
14+ if ( typeof child === "string" ) {
15+ const text = { text : child } ;
16+ STRINGS . add ( text ) ;
17+ child = text ;
18+ }
19+
20+ if ( isText ( child ) ) {
21+ const c = child ; // HACK: fix typescript complaining
22+
23+ if (
24+ isText ( prev ) &&
25+ STRINGS . has ( prev ) &&
26+ STRINGS . has ( c ) &&
27+ textEquals ( prev , c , { loose : true } )
28+ ) {
29+ prev . text += c . text ;
30+ } else {
31+ nodes . push ( c ) ;
32+ }
33+ } else if ( isElement ( child ) ) {
34+ nodes . push ( child ) ;
35+ } else {
36+ throw new Error ( `Unexpected hyperscript child object: ${ child } ` ) ;
37+ }
38+ } ;
39+
40+ for ( const child of children . flat ( Infinity ) ) {
41+ addChild ( child ) ;
42+ }
43+
44+ return nodes ;
45+ } ;
46+
47+ /**
48+ * Create an `Element` object.
49+ */
50+
51+ export function createElement (
52+ tagName : string ,
53+ attributes : { [ key : string ] : any } ,
54+ children : any [ ]
55+ ) {
56+ return { ...attributes , children : resolveDescendants ( children ) } ;
57+ }
58+
59+ /**
60+ * Create a fragment.
61+ */
62+
63+ export function createFragment (
64+ tagName : string ,
65+ attributes : { [ key : string ] : any } ,
66+ children : any [ ]
67+ ) {
68+ return resolveDescendants ( children ) ;
69+ }
70+
71+ /**
72+ * Create a `Text` object.
73+ */
74+
75+ export function createText (
76+ tagName : string ,
77+ attributes : { [ key : string ] : any } ,
78+ children : any [ ]
79+ ) {
80+ const nodes = resolveDescendants ( children ) ;
81+
82+ if ( nodes . length > 1 ) {
83+ throw new Error (
84+ `The <text> hyperscript tag must only contain a single node's worth of children.`
85+ ) ;
86+ }
87+
88+ let [ node ] = nodes ;
89+
90+ if ( node == null ) {
91+ node = { text : "" } ;
92+ }
93+
94+ if ( ! isText ( node ) ) {
95+ throw new Error ( `
96+ The <text> hyperscript tag can only contain text content as children.` ) ;
97+ }
98+
99+ // COMPAT: If they used the <text> tag we want to guarantee that it won't be
100+ // merge with other string children.
101+ STRINGS . delete ( node ) ;
102+
103+ Object . assign ( node , attributes ) ;
104+ return node ;
105+ }
106+
107+ const STRINGS = new WeakSet ( ) ;
108+
109+ function isText ( value : any ) {
110+ return isPlainObject ( value ) && typeof value . text === "string" ;
111+ }
112+
113+ function textEquals (
114+ text : any ,
115+ another : any ,
116+ options : { loose ?: boolean } = { }
117+ ) : boolean {
118+ const { loose = false } = options ;
119+
120+ function omitText ( obj : Record < any , any > ) {
121+ const { text, ...rest } = obj ;
122+
123+ return rest ;
124+ }
125+
126+ return isEqual (
127+ loose ? omitText ( text ) : text ,
128+ loose ? omitText ( another ) : another
129+ ) ;
130+ }
131+
132+ const isElement = ( value : any ) : boolean => {
133+ return (
134+ isPlainObject ( value ) &&
135+ isNodeList ( value . children )
136+ // && !Editor.isEditor(value) // value cannot be editor
137+ )
138+ } ;
139+
140+ export const jsx = (
141+ tagName : string ,
142+ attributes ?: Object ,
143+ ...children : any [ ]
144+ ) => {
145+ const creators = {
146+ element : createElement ,
147+ fragment : createFragment ,
148+ text : createText ,
149+ } ;
150+ const creator = creators [ tagName ] ;
151+
152+ if ( ! creator ) {
153+ throw new Error ( `No hyperscript creator found for tag: <${ tagName } >` ) ;
154+ }
155+
156+ if ( attributes == null ) {
157+ attributes = { } ;
158+ }
159+
160+ if ( ! isPlainObject ( attributes ) ) {
161+ children = [ attributes ] . concat ( children ) ;
162+ attributes = { } ;
163+ }
164+
165+ children = children . filter ( ( child ) => Boolean ( child ) ) . flat ( ) ;
166+ const ret = creator ( tagName , attributes , children ) ;
167+ return ret ;
168+ } ;
169+
170+
171+ function isNodeList ( value : any [ ] ) {
172+ return value . every ( val => isNode ( val ) )
173+ }
174+
175+ function isNode ( value : any ) {
176+ return (
177+ isText ( value ) || isElement ( value )
178+ // || Editor.isEditor(value) // // value cannot be editor
179+ )
180+ }
0 commit comments