bruh 1.8.1 → 1.10.0
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/dist/bruh.es.js +358 -1
- package/dist/bruh.es.js.map +1 -1
- package/dist/bruh.umd.js +1 -1
- package/dist/bruh.umd.js.map +1 -1
- package/package.json +5 -13
- package/src/components/optimized-picture/render.mjs +4 -1
- package/src/dom/index.browser.mjs +124 -153
- package/src/dom/index.node.mjs +168 -100
- package/src/dom/live-fragment.mjs +2 -4
- package/src/reactive/index.mjs +2 -2
- package/src/util/index.mjs +0 -14
- package/src/reactive/index.d.ts +0 -31
package/src/dom/index.node.mjs
CHANGED
|
@@ -1,3 +1,10 @@
|
|
|
1
|
+
const isMetaNode = Symbol.for("bruh meta node")
|
|
2
|
+
const isMetaTextNode = Symbol.for("bruh meta text node")
|
|
3
|
+
const isMetaElement = Symbol.for("bruh meta element")
|
|
4
|
+
const isMetaRawString = Symbol.for("bruh meta raw string")
|
|
5
|
+
|
|
6
|
+
//#region HTML syntax functions
|
|
7
|
+
|
|
1
8
|
// https://html.spec.whatwg.org/multipage/syntax.html#void-elements
|
|
2
9
|
const voidElements = [
|
|
3
10
|
"base",
|
|
@@ -26,6 +33,7 @@ const isVoidElement = element =>
|
|
|
26
33
|
|
|
27
34
|
// https://html.spec.whatwg.org/multipage/syntax.html#elements-2
|
|
28
35
|
// https://html.spec.whatwg.org/multipage/syntax.html#cdata-rcdata-restrictions
|
|
36
|
+
// Does not work for https://html.spec.whatwg.org/multipage/syntax.html#raw-text-elements (script and style)
|
|
29
37
|
const escapeForElement = x =>
|
|
30
38
|
(x + "")
|
|
31
39
|
.replace(/&/g, "&")
|
|
@@ -37,172 +45,232 @@ const escapeForDoubleQuotedAttribute = x =>
|
|
|
37
45
|
.replace(/&/g, "&")
|
|
38
46
|
.replace(/"/g, """)
|
|
39
47
|
|
|
40
|
-
|
|
41
|
-
const
|
|
42
|
-
|
|
43
|
-
|
|
48
|
+
// https://html.spec.whatwg.org/multipage/syntax.html#attributes-2
|
|
49
|
+
const attributesToString = attributes =>
|
|
50
|
+
Object.entries(attributes)
|
|
51
|
+
.map(([name, value]) =>
|
|
52
|
+
value === ""
|
|
53
|
+
? ` ${name}`
|
|
54
|
+
: ` ${name}="${escapeForDoubleQuotedAttribute(value)}"`
|
|
55
|
+
).join("")
|
|
56
|
+
|
|
57
|
+
//#endregion
|
|
44
58
|
|
|
45
59
|
// A basic check for if a value is allowed as a meta node's child
|
|
46
60
|
// It's responsible for quickly checking the type, not deep validation
|
|
47
|
-
const
|
|
61
|
+
const isMetaChild = x =>
|
|
48
62
|
// meta nodes, reactives, and DOM nodes
|
|
49
63
|
x?.[isMetaNode] ||
|
|
50
64
|
x?.[isMetaRawString] ||
|
|
51
65
|
// Any array, just assume it contains valid children
|
|
52
66
|
Array.isArray(x) ||
|
|
53
|
-
//
|
|
54
|
-
|
|
67
|
+
// Allow nullish
|
|
68
|
+
x == null ||
|
|
69
|
+
// Disallow functions and objects
|
|
70
|
+
!(typeof x === "function" || typeof x === "object")
|
|
71
|
+
// Everything else can be a child when stringified
|
|
55
72
|
|
|
56
73
|
|
|
57
|
-
|
|
74
|
+
//#region Meta Nodes that act like lightweight rendering-oriented DOM nodes
|
|
58
75
|
|
|
76
|
+
// Text nodes have no individual HTML representation
|
|
77
|
+
// We emulate this with a custom element <bruh-textnode> with an inline style reset
|
|
78
|
+
// These elements can be hydrated very quickly and even be marked with a tag
|
|
59
79
|
export class MetaTextNode {
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
80
|
+
[isMetaNode] = true;
|
|
81
|
+
[isMetaTextNode] = true
|
|
82
|
+
|
|
83
|
+
textContent
|
|
84
|
+
tag
|
|
63
85
|
|
|
86
|
+
constructor(textContent) {
|
|
64
87
|
this.textContent = textContent
|
|
65
|
-
this.tag = undefined
|
|
66
88
|
}
|
|
67
89
|
|
|
68
90
|
toString() {
|
|
69
|
-
|
|
70
|
-
this.tag
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
91
|
+
const tag = this.tag
|
|
92
|
+
? ` tag="${escapeForDoubleQuotedAttribute(this.tag)}"`
|
|
93
|
+
: ""
|
|
94
|
+
return `<bruh-textnode style="all:unset;display:inline"${tag}>${
|
|
95
|
+
escapeForElement(this.textContent)
|
|
96
|
+
}</bruh-textnode>`
|
|
74
97
|
}
|
|
75
98
|
|
|
76
|
-
setTag(tag
|
|
99
|
+
setTag(tag) {
|
|
77
100
|
this.tag = tag
|
|
78
101
|
|
|
79
102
|
return this
|
|
80
103
|
}
|
|
81
104
|
}
|
|
82
105
|
|
|
106
|
+
// A light model of an element
|
|
83
107
|
export class MetaElement {
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
this[isMetaElement] = true
|
|
108
|
+
[isMetaNode] = true;
|
|
109
|
+
[isMetaElement] = true
|
|
87
110
|
|
|
88
|
-
|
|
89
|
-
|
|
111
|
+
name
|
|
112
|
+
attributes = {}
|
|
113
|
+
children = []
|
|
90
114
|
|
|
91
|
-
|
|
92
|
-
this.
|
|
115
|
+
constructor(name) {
|
|
116
|
+
this.name = name
|
|
93
117
|
}
|
|
94
118
|
|
|
95
119
|
toString() {
|
|
96
|
-
|
|
97
|
-
const attributes =
|
|
98
|
-
[
|
|
99
|
-
...Object.entries(this.attributes),
|
|
100
|
-
...Object.entries(this.dataset)
|
|
101
|
-
.map(([name, value]) => {
|
|
102
|
-
// https://html.spec.whatwg.org/multipage/dom.html#dom-domstringmap-setitem
|
|
103
|
-
const skewered = name.replace(/[A-Z]/g, letter => `-${letter.toLowerCase()}`)
|
|
104
|
-
return [`data-${skewered}`, value]
|
|
105
|
-
})
|
|
106
|
-
]
|
|
107
|
-
.map(([name, value]) =>
|
|
108
|
-
value === ""
|
|
109
|
-
? ` ${name}`
|
|
110
|
-
: ` ${name}="${escapeForDoubleQuotedAttribute(value)}"`
|
|
111
|
-
).join("")
|
|
120
|
+
const attributes = attributesToString(this.attributes)
|
|
112
121
|
// https://html.spec.whatwg.org/multipage/syntax.html#syntax-start-tag
|
|
113
122
|
const startTag = `<${this.name}${attributes}>`
|
|
114
|
-
|
|
115
123
|
if (isVoidElement(this.name))
|
|
116
124
|
return startTag
|
|
117
125
|
|
|
118
|
-
const contents =
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
+
const contents = this.children
|
|
127
|
+
.flat(Infinity)
|
|
128
|
+
.map(child =>
|
|
129
|
+
(child[isMetaNode] || child[isMetaRawString])
|
|
130
|
+
? child.toString()
|
|
131
|
+
: escapeForElement(child)
|
|
132
|
+
)
|
|
133
|
+
.join("")
|
|
126
134
|
// https://html.spec.whatwg.org/multipage/syntax.html#end-tags
|
|
127
135
|
const endTag = `</${this.name}>`
|
|
128
136
|
return startTag + contents + endTag
|
|
129
137
|
}
|
|
138
|
+
}
|
|
130
139
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
addDataAttributes(dataAttributes = {}) {
|
|
138
|
-
Object.assign(this.dataset, dataAttributes)
|
|
140
|
+
// Raw strings can be meta element children, where they bypass string escaping
|
|
141
|
+
// This should be avoided in general, but is needed for unsupported HTML features
|
|
142
|
+
export class MetaRawString extends String {
|
|
143
|
+
[isMetaRawString] = true
|
|
139
144
|
|
|
140
|
-
|
|
145
|
+
constructor(string) {
|
|
146
|
+
super(string)
|
|
141
147
|
}
|
|
142
148
|
}
|
|
143
149
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
150
|
+
//#endregion
|
|
151
|
+
|
|
152
|
+
//#region Meta element helper functions e.g. applyAttributes()
|
|
153
|
+
|
|
154
|
+
// Merge style rules with an object
|
|
155
|
+
export const applyStyles = (element, styles) => {
|
|
156
|
+
// Doesn't support proper escaping
|
|
157
|
+
// https://www.w3.org/TR/css-syntax-3/#ref-for-parse-a-list-of-declarations%E2%91%A0
|
|
158
|
+
// https://www.w3.org/TR/css-syntax-3/#typedef-ident-token
|
|
159
|
+
const currentStyles = Object.fromEntries(
|
|
160
|
+
(element.attributes.style || "")
|
|
161
|
+
.split(";").filter(s => s.length)
|
|
162
|
+
.map(declaration => declaration.split(":").map(s => s.trim()))
|
|
163
|
+
)
|
|
164
|
+
|
|
165
|
+
Object.entries(styles)
|
|
166
|
+
.forEach(([property, value]) => {
|
|
167
|
+
if (value !== undefined)
|
|
168
|
+
currentStyles[property] = value
|
|
169
|
+
else
|
|
170
|
+
delete currentStyles[property]
|
|
171
|
+
})
|
|
172
|
+
|
|
173
|
+
element.attributes.style =
|
|
174
|
+
Object.entries(currentStyles)
|
|
175
|
+
.map(([property, value]) => `${property}:${value}`)
|
|
176
|
+
.join(";")
|
|
177
|
+
}
|
|
149
178
|
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
179
|
+
// Merge classes with an object mapping from class names to booleans
|
|
180
|
+
export const applyClasses = (element, classes) => {
|
|
181
|
+
// Doesn't support proper escaping
|
|
182
|
+
// https://html.spec.whatwg.org/multipage/dom.html#global-attributes:classes-2
|
|
183
|
+
const currentClasses = new Set(
|
|
184
|
+
(element.attributes.class || "")
|
|
185
|
+
.split(/\s+/).filter(s => s.length)
|
|
186
|
+
)
|
|
187
|
+
|
|
188
|
+
Object.entries(classes)
|
|
189
|
+
.forEach(([name, value]) => {
|
|
190
|
+
if (value)
|
|
191
|
+
currentClasses.add(name)
|
|
192
|
+
else
|
|
193
|
+
currentClasses.delete(name)
|
|
194
|
+
})
|
|
195
|
+
|
|
196
|
+
element.attributes.class = [...currentClasses].join(" ")
|
|
153
197
|
}
|
|
154
198
|
|
|
199
|
+
// Merge attributes with an object
|
|
200
|
+
export const applyAttributes = (element, attributes) => {
|
|
201
|
+
Object.entries(attributes)
|
|
202
|
+
.forEach(([name, value]) => {
|
|
203
|
+
if (value !== undefined)
|
|
204
|
+
element.attributes[name] = value
|
|
205
|
+
else
|
|
206
|
+
delete element.attributes[name]
|
|
207
|
+
})
|
|
208
|
+
}
|
|
155
209
|
|
|
210
|
+
//#endregion
|
|
156
211
|
|
|
157
|
-
|
|
212
|
+
//#region rawString(), t(), and e()
|
|
158
213
|
|
|
159
214
|
export const rawString = string =>
|
|
160
215
|
new MetaRawString(string)
|
|
161
216
|
|
|
162
|
-
const
|
|
217
|
+
export const t = textContent =>
|
|
163
218
|
new MetaTextNode(textContent)
|
|
164
219
|
|
|
165
|
-
const
|
|
166
|
-
const
|
|
220
|
+
export const e = name => (...variadic) => {
|
|
221
|
+
const element = new MetaElement(name)
|
|
167
222
|
|
|
168
|
-
//
|
|
169
|
-
if (
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
meta.attributes = {}
|
|
173
|
-
meta.children = variadic
|
|
223
|
+
// If there are no props
|
|
224
|
+
if (isMetaChild(variadic[0])) {
|
|
225
|
+
element.children.push(...variadic)
|
|
226
|
+
return element
|
|
174
227
|
}
|
|
175
228
|
|
|
176
|
-
|
|
177
|
-
|
|
229
|
+
// If props exist as the first variadic argument
|
|
230
|
+
const [props, ...children] = variadic
|
|
178
231
|
|
|
179
|
-
//
|
|
180
|
-
|
|
181
|
-
// If we are making a html element
|
|
182
|
-
// This is likely when the jsx tag name begins with a lowercase character
|
|
183
|
-
if (typeof nameOrComponent == "string") {
|
|
184
|
-
const meta = new MetaElement(nameOrComponent)
|
|
232
|
+
// The bruh prop is reserved for future use
|
|
233
|
+
delete props.bruh
|
|
185
234
|
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
235
|
+
// Apply overloaded props, if possible
|
|
236
|
+
if (typeof props.style === "object") {
|
|
237
|
+
applyStyles(element, props.style)
|
|
238
|
+
delete props.style
|
|
239
|
+
}
|
|
240
|
+
if (typeof props.class === "object") {
|
|
241
|
+
applyClasses(element, props.class)
|
|
242
|
+
delete props.class
|
|
191
243
|
}
|
|
244
|
+
// The rest of the props are attributes
|
|
245
|
+
applyAttributes(element, props)
|
|
192
246
|
|
|
193
|
-
//
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
// This object includes the all of the attributes and a "children" key
|
|
197
|
-
return nameOrComponent( Object.assign({}, attributesOrProps, { children }) )
|
|
247
|
+
// Add the children to the element
|
|
248
|
+
element.children.push(...children)
|
|
249
|
+
return element
|
|
198
250
|
}
|
|
199
251
|
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
252
|
+
//#endregion
|
|
253
|
+
|
|
254
|
+
//#region JSX integration
|
|
255
|
+
|
|
256
|
+
// The function that jsx tags (except fragments) compile to
|
|
257
|
+
export const h = (nameOrComponent, props, ...children) => {
|
|
258
|
+
// If we are making an element, this is just a wrapper of e()
|
|
259
|
+
// This is likely when the JSX tag name begins with a lowercase character
|
|
260
|
+
if (typeof nameOrComponent === "string") {
|
|
261
|
+
const makeElement = e(nameOrComponent)
|
|
262
|
+
return props
|
|
263
|
+
? makeElement(props, ...children)
|
|
264
|
+
: makeElement(...children)
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
// It must be a component, then, as bruh components are just functions
|
|
268
|
+
// Due to JSX, this would mean a function with only one parameter - props
|
|
269
|
+
// This object includes the all of the normal props and a "children" key
|
|
270
|
+
return nameOrComponent({ ...props, children })
|
|
205
271
|
}
|
|
206
272
|
|
|
207
273
|
// The JSX fragment is made into a bruh fragment (just an array)
|
|
208
274
|
export const JSXFragment = ({ children }) => children
|
|
275
|
+
|
|
276
|
+
//#endregion
|
|
@@ -9,10 +9,8 @@
|
|
|
9
9
|
// Also, make sure not to call .normalize() on the parent element,
|
|
10
10
|
// because that would ruin the placeholders.
|
|
11
11
|
export class LiveFragment {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
this.endMarker = document.createTextNode("")
|
|
15
|
-
}
|
|
12
|
+
startMarker = document.createTextNode("")
|
|
13
|
+
endMarker = document.createTextNode("")
|
|
16
14
|
|
|
17
15
|
static from(firstNode, lastNode) {
|
|
18
16
|
const liveFragment = new this()
|
package/src/reactive/index.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
const isReactive = Symbol.for("bruh reactive")
|
|
1
|
+
export const isReactive = Symbol.for("bruh reactive")
|
|
2
2
|
|
|
3
3
|
// A super simple and performant reactive value implementation
|
|
4
4
|
export class SimpleReactive {
|
|
@@ -79,7 +79,7 @@ export class FunctionalReactive {
|
|
|
79
79
|
}
|
|
80
80
|
|
|
81
81
|
set value(newValue) {
|
|
82
|
-
// Only allow
|
|
82
|
+
// Only allow source nodes to be directly updated
|
|
83
83
|
if (this.#depth !== 0)
|
|
84
84
|
return
|
|
85
85
|
|
package/src/util/index.mjs
CHANGED
|
@@ -31,20 +31,6 @@ export const createDestructable = (object, iterable) => {
|
|
|
31
31
|
return destructable
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
-
// A function that acts like Maybe.from(x).ifExists(existsThen).ifEmpty(emptyThen)
|
|
35
|
-
// Except we just use an array in place of a true Maybe type
|
|
36
|
-
// This is useful for setting and removing reactive attributes
|
|
37
|
-
export const maybeDo = (existsThen, emptyThen) => x => {
|
|
38
|
-
if (Array.isArray(x)) {
|
|
39
|
-
if (x.length)
|
|
40
|
-
existsThen(x[0])
|
|
41
|
-
else
|
|
42
|
-
emptyThen()
|
|
43
|
-
}
|
|
44
|
-
else
|
|
45
|
-
existsThen(x)
|
|
46
|
-
}
|
|
47
|
-
|
|
48
34
|
// Creates an object (as a Proxy) that acts as a function
|
|
49
35
|
// So functionAsObject(f).property is equivalent to f("property")
|
|
50
36
|
// This is can be useful when combined with destructuring syntax, e.g.:
|
package/src/reactive/index.d.ts
DELETED
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
export declare const isReactive: unique symbol
|
|
2
|
-
|
|
3
|
-
export declare type Reactive<T> = {
|
|
4
|
-
[isReactive]: true
|
|
5
|
-
value: T
|
|
6
|
-
addReaction: (reaction: Function) => Function
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
export declare const reactiveDo: {
|
|
10
|
-
<T>(reactive: Reactive<T>, f: (value: T) => unknown): Function
|
|
11
|
-
<T>(value: T, f: (value: T) => unknown): void
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
export declare class SimpleReactive<T> implements Reactive<T> {
|
|
15
|
-
constructor(value: T)
|
|
16
|
-
|
|
17
|
-
[isReactive]: true
|
|
18
|
-
get value(): T
|
|
19
|
-
set value(newValue: T)
|
|
20
|
-
addReaction(reaction: Function): () => boolean
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
export declare class FunctionalReactive<T> implements Reactive<T> {
|
|
24
|
-
constructor(value: T)
|
|
25
|
-
constructor(dependencies: Iterable<FunctionalReactive<unknown>>, f: () => T)
|
|
26
|
-
|
|
27
|
-
[isReactive]: true
|
|
28
|
-
get value(): T
|
|
29
|
-
set value(newValue: T)
|
|
30
|
-
addReaction(reaction: Function): () => boolean
|
|
31
|
-
}
|