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.
@@ -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
- const isMetaNode = Symbol.for("bruh meta node")
41
- const isMetaTextNode = Symbol.for("bruh meta text node")
42
- const isMetaElement = Symbol.for("bruh meta element")
43
- const isMetaRawString = Symbol.for("bruh meta raw string")
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 isMetaNodeChild = x =>
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
- // Everything else, as long as it isn't a function, can be a child when stringified
54
- typeof x !== "function"
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
- // Meta Nodes
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
- return `<bruh-textnode style="all:unset;display:inline"${
72
- this.tag
73
- ? ` data-tag="${escapeForDoubleQuotedAttribute(this.tag)}"`
74
- : ""
75
- }>${ escapeForElement(this.textContent) }</bruh-textnode>`
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
- // https://html.spec.whatwg.org/multipage/syntax.html#attributes-2
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
- this.children
114
- .flat(Infinity)
115
- .map(child => {
116
- return (child[isMetaNode] || child[isMetaRawString])
117
- ? child.toString()
118
- : escapeForElement(child)
119
- }).join("")
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
- export class MetaRawString {
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
- this.string = string
146
+ super(string)
191
147
  }
148
+ }
192
149
 
193
- toString() {
194
- return this.string
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
- // Convenience functions
212
+ //#region rawString(), t(), and e()
201
213
 
202
214
  export const rawString = string =>
203
215
  new MetaRawString(string)
204
216
 
205
- const createMetaTextNode = textContent =>
217
+ export const t = textContent =>
206
218
  new MetaTextNode(textContent)
207
219
 
208
- const createMetaElement = name => (...variadic) => {
209
- const meta = new MetaElement(name)
220
+ export const e = name => (...variadic) => {
221
+ const element = new MetaElement(name)
210
222
 
211
- // Implement optional attributes as first argument
212
- if (!isMetaNodeChild(variadic[0]))
213
- [meta.attributes, ...meta.children] = variadic
214
- else {
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
- return meta
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
- // These are attributes then, but they might be null/undefined
230
- meta.attributes = attributesOrProps || {}
231
- meta.children = children
232
+ // The bruh prop is reserved for future use
233
+ delete props.bruh
232
234
 
233
- return meta
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
- // It must be a component, then
237
- // Bruh components are just functions that return meta elements
238
- // Due to JSX, this would mean a function with only one parameter - a "props" object
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
- // These will be called with short names
244
- export {
245
- createMetaTextNode as t,
246
- createMetaElement as e,
247
- createMetaElementJSX as h
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
@@ -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, go ahead and apply them first
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
- FunctionalReactive.applyUpdates()
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 souce nodes to be directly updated
84
+ // Only allow source nodes to be directly updated
83
85
  if (this.#depth !== 0)
84
86
  return
85
87
 
@@ -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.:
@@ -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
- }