bruh 1.9.0 → 1.10.1
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 +360 -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 +7 -14
- package/src/components/optimized-picture/render.mjs +4 -1
- package/src/dom/index.browser.mjs +123 -177
- package/src/dom/index.node.mjs +154 -129
- package/src/reactive/index.mjs +6 -4
- package/src/util/index.mjs +0 -14
- package/src/reactive/index.d.ts +0 -36
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,25 +45,37 @@ 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
80
|
[isMetaNode] = true;
|
|
61
81
|
[isMetaTextNode] = true
|
|
@@ -68,20 +88,22 @@ export class MetaTextNode {
|
|
|
68
88
|
}
|
|
69
89
|
|
|
70
90
|
toString() {
|
|
71
|
-
|
|
72
|
-
this.tag
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
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>`
|
|
76
97
|
}
|
|
77
98
|
|
|
78
|
-
setTag(tag
|
|
99
|
+
setTag(tag) {
|
|
79
100
|
this.tag = tag
|
|
80
101
|
|
|
81
102
|
return this
|
|
82
103
|
}
|
|
83
104
|
}
|
|
84
105
|
|
|
106
|
+
// A light model of an element
|
|
85
107
|
export class MetaElement {
|
|
86
108
|
[isMetaNode] = true;
|
|
87
109
|
[isMetaElement] = true
|
|
@@ -95,157 +117,160 @@ export class MetaElement {
|
|
|
95
117
|
}
|
|
96
118
|
|
|
97
119
|
toString() {
|
|
98
|
-
|
|
99
|
-
const attributes =
|
|
100
|
-
Object.entries(this.attributes)
|
|
101
|
-
.map(([name, value]) =>
|
|
102
|
-
value === ""
|
|
103
|
-
? ` ${name}`
|
|
104
|
-
: ` ${name}="${escapeForDoubleQuotedAttribute(value)}"`
|
|
105
|
-
).join("")
|
|
120
|
+
const attributes = attributesToString(this.attributes)
|
|
106
121
|
// https://html.spec.whatwg.org/multipage/syntax.html#syntax-start-tag
|
|
107
122
|
const startTag = `<${this.name}${attributes}>`
|
|
108
|
-
|
|
109
123
|
if (isVoidElement(this.name))
|
|
110
124
|
return startTag
|
|
111
125
|
|
|
112
|
-
const contents =
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
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("")
|
|
120
134
|
// https://html.spec.whatwg.org/multipage/syntax.html#end-tags
|
|
121
135
|
const endTag = `</${this.name}>`
|
|
122
136
|
return startTag + contents + endTag
|
|
123
137
|
}
|
|
124
|
-
|
|
125
|
-
addAttributes(attributes = {}) {
|
|
126
|
-
Object.assign(this.attributes, attributes)
|
|
127
|
-
|
|
128
|
-
return this
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
addDataAttributes(dataAttributes = {}) {
|
|
132
|
-
Object.entries(dataAttributes)
|
|
133
|
-
.forEach(([name, value]) => {
|
|
134
|
-
// https://html.spec.whatwg.org/multipage/dom.html#dom-domstringmap-setitem
|
|
135
|
-
const skewered = name.replace(/[A-Z]/g, letter => `-${letter.toLowerCase()}`)
|
|
136
|
-
this.attributes[`data-${skewered}`] = value
|
|
137
|
-
})
|
|
138
|
-
|
|
139
|
-
return this
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
addStyles(styles = {}) {
|
|
143
|
-
// Doesn't support proper escaping
|
|
144
|
-
// https://www.w3.org/TR/css-syntax-3/#ref-for-parse-a-list-of-declarations%E2%91%A0
|
|
145
|
-
// https://www.w3.org/TR/css-syntax-3/#typedef-ident-token
|
|
146
|
-
const currentStyles = Object.fromEntries(
|
|
147
|
-
(this.attributes.style || "")
|
|
148
|
-
.split(";").filter(s => s.length)
|
|
149
|
-
.map(declaration => declaration.split(":").map(s => s.trim()))
|
|
150
|
-
)
|
|
151
|
-
|
|
152
|
-
Object.assign(currentStyles, styles)
|
|
153
|
-
|
|
154
|
-
this.attributes.style =
|
|
155
|
-
Object.entries(currentStyles)
|
|
156
|
-
.map(([property, value]) => `${property}:${value}`)
|
|
157
|
-
.join(";")
|
|
158
|
-
|
|
159
|
-
return this
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
toggleClasses(classes = {}) {
|
|
163
|
-
// Doesn't support proper escaping
|
|
164
|
-
// https://html.spec.whatwg.org/multipage/dom.html#global-attributes:classes-2
|
|
165
|
-
const classList = new Set(
|
|
166
|
-
(this.attributes.class || "")
|
|
167
|
-
.split(/\s+/).filter(s => s.length)
|
|
168
|
-
)
|
|
169
|
-
|
|
170
|
-
Object.entries(classes)
|
|
171
|
-
.forEach(([name, value]) => {
|
|
172
|
-
if (value)
|
|
173
|
-
classList.add(name)
|
|
174
|
-
else
|
|
175
|
-
classList.delete(name)
|
|
176
|
-
})
|
|
177
|
-
|
|
178
|
-
this.attributes.class = Array.from(classList).join(" ")
|
|
179
|
-
|
|
180
|
-
return this
|
|
181
|
-
}
|
|
182
138
|
}
|
|
183
139
|
|
|
184
|
-
|
|
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 {
|
|
185
143
|
[isMetaRawString] = true
|
|
186
144
|
|
|
187
|
-
string
|
|
188
|
-
|
|
189
145
|
constructor(string) {
|
|
190
|
-
|
|
146
|
+
super(string)
|
|
191
147
|
}
|
|
148
|
+
}
|
|
192
149
|
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
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(";")
|
|
196
177
|
}
|
|
197
178
|
|
|
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(" ")
|
|
197
|
+
}
|
|
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
|
+
}
|
|
198
209
|
|
|
210
|
+
//#endregion
|
|
199
211
|
|
|
200
|
-
|
|
212
|
+
//#region rawString(), t(), and e()
|
|
201
213
|
|
|
202
214
|
export const rawString = string =>
|
|
203
215
|
new MetaRawString(string)
|
|
204
216
|
|
|
205
|
-
const
|
|
217
|
+
export const t = textContent =>
|
|
206
218
|
new MetaTextNode(textContent)
|
|
207
219
|
|
|
208
|
-
const
|
|
209
|
-
const
|
|
220
|
+
export const e = name => (...variadic) => {
|
|
221
|
+
const element = new MetaElement(name)
|
|
210
222
|
|
|
211
|
-
//
|
|
212
|
-
if (
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
meta.attributes = {}
|
|
216
|
-
meta.children = variadic
|
|
223
|
+
// If there are no props
|
|
224
|
+
if (isMetaChild(variadic[0])) {
|
|
225
|
+
element.children.push(...variadic)
|
|
226
|
+
return element
|
|
217
227
|
}
|
|
218
228
|
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
// JSX integration
|
|
223
|
-
const createMetaElementJSX = (nameOrComponent, attributesOrProps, ...children) => {
|
|
224
|
-
// If we are making a html element
|
|
225
|
-
// This is likely when the jsx tag name begins with a lowercase character
|
|
226
|
-
if (typeof nameOrComponent == "string") {
|
|
227
|
-
const meta = new MetaElement(nameOrComponent)
|
|
229
|
+
// If props exist as the first variadic argument
|
|
230
|
+
const [props, ...children] = variadic
|
|
228
231
|
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
meta.children = children
|
|
232
|
+
// The bruh prop is reserved for future use
|
|
233
|
+
delete props.bruh
|
|
232
234
|
|
|
233
|
-
|
|
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
|
|
234
243
|
}
|
|
244
|
+
// The rest of the props are attributes
|
|
245
|
+
applyAttributes(element, props)
|
|
235
246
|
|
|
236
|
-
//
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
// This object includes the all of the attributes and a "children" key
|
|
240
|
-
return nameOrComponent( Object.assign({}, attributesOrProps, { children }) )
|
|
247
|
+
// Add the children to the element
|
|
248
|
+
element.children.push(...children)
|
|
249
|
+
return element
|
|
241
250
|
}
|
|
242
251
|
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
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 })
|
|
248
271
|
}
|
|
249
272
|
|
|
250
273
|
// The JSX fragment is made into a bruh fragment (just an array)
|
|
251
274
|
export const JSXFragment = ({ children }) => children
|
|
275
|
+
|
|
276
|
+
//#endregion
|
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 {
|
|
@@ -70,16 +70,18 @@ export class FunctionalReactive {
|
|
|
70
70
|
}
|
|
71
71
|
|
|
72
72
|
get value() {
|
|
73
|
-
// If there are any pending updates,
|
|
73
|
+
// If there are any relevant pending updates, apply them first
|
|
74
74
|
// It's ok that there's already a microtask queued for this
|
|
75
75
|
if (FunctionalReactive.#settersQueue.size)
|
|
76
|
-
|
|
76
|
+
// Heuristic quick check
|
|
77
|
+
if (this.#depth !== 0 || FunctionalReactive.#settersQueue.has(this))
|
|
78
|
+
FunctionalReactive.applyUpdates()
|
|
77
79
|
|
|
78
80
|
return this.#value
|
|
79
81
|
}
|
|
80
82
|
|
|
81
83
|
set value(newValue) {
|
|
82
|
-
// Only allow
|
|
84
|
+
// Only allow source nodes to be directly updated
|
|
83
85
|
if (this.#depth !== 0)
|
|
84
86
|
return
|
|
85
87
|
|
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,36 +0,0 @@
|
|
|
1
|
-
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: Array<FunctionalReactive<unknown>>, f: () => T)
|
|
26
|
-
|
|
27
|
-
[isReactive]: true
|
|
28
|
-
get value(): T
|
|
29
|
-
set value(newValue: T)
|
|
30
|
-
addReaction(reaction: Function): () => boolean
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
export declare const r: {
|
|
34
|
-
<T>(value: T): FunctionalReactive<T>
|
|
35
|
-
<T>(dependencies: Array<FunctionalReactive<unknown>>, f: () => T): FunctionalReactive<T>
|
|
36
|
-
}
|