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.
@@ -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
- 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
- constructor(textContent) {
61
- this[isMetaNode] =
62
- this[isMetaTextNode] = true
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
- return `<bruh-textnode style="all:unset;display:inline"${
70
- this.tag
71
- ? ` data-tag="${escapeForDoubleQuotedAttribute(this.tag)}"`
72
- : ""
73
- }>${ 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>`
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
- constructor(name) {
85
- this[isMetaNode] =
86
- this[isMetaElement] = true
108
+ [isMetaNode] = true;
109
+ [isMetaElement] = true
87
110
 
88
- this.name = name
89
- this.children = []
111
+ name
112
+ attributes = {}
113
+ children = []
90
114
 
91
- this.attributes = {}
92
- this.dataset = {}
115
+ constructor(name) {
116
+ this.name = name
93
117
  }
94
118
 
95
119
  toString() {
96
- // https://html.spec.whatwg.org/multipage/syntax.html#attributes-2
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
- this.children
120
- .flat(Infinity)
121
- .map(child => {
122
- return (child[isMetaNode] || child[isMetaRawString])
123
- ? child.toString()
124
- : escapeForElement(child)
125
- }).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("")
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
- addAttributes(attributes = {}) {
132
- Object.assign(this.attributes, attributes)
133
-
134
- return this
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
- return this
145
+ constructor(string) {
146
+ super(string)
141
147
  }
142
148
  }
143
149
 
144
- export class MetaRawString {
145
- constructor(string) {
146
- this[isMetaRawString] = true
147
- this.string = string
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
- toString() {
151
- return this.string
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
- // Convenience functions
212
+ //#region rawString(), t(), and e()
158
213
 
159
214
  export const rawString = string =>
160
215
  new MetaRawString(string)
161
216
 
162
- const createMetaTextNode = textContent =>
217
+ export const t = textContent =>
163
218
  new MetaTextNode(textContent)
164
219
 
165
- const createMetaElement = name => (...variadic) => {
166
- const meta = new MetaElement(name)
220
+ export const e = name => (...variadic) => {
221
+ const element = new MetaElement(name)
167
222
 
168
- // Implement optional attributes as first argument
169
- if (!isMetaNodeChild(variadic[0]))
170
- [meta.attributes, ...meta.children] = variadic
171
- else {
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
- return meta
177
- }
229
+ // If props exist as the first variadic argument
230
+ const [props, ...children] = variadic
178
231
 
179
- // JSX integration
180
- const createMetaElementJSX = (nameOrComponent, attributesOrProps, ...children) => {
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
- // These are attributes then, but they might be null/undefined
187
- meta.attributes = attributesOrProps || {}
188
- meta.children = children
189
-
190
- 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
191
243
  }
244
+ // The rest of the props are attributes
245
+ applyAttributes(element, props)
192
246
 
193
- // It must be a component, then
194
- // Bruh components are just functions that return meta elements
195
- // Due to JSX, this would mean a function with only one parameter - a "props" object
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
- // These will be called with short names
201
- export {
202
- createMetaTextNode as t,
203
- createMetaElement as e,
204
- 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 })
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
- constructor() {
13
- this.startMarker = document.createTextNode("")
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()
@@ -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 souce nodes to be directly updated
82
+ // Only allow source nodes to be directly updated
83
83
  if (this.#depth !== 0)
84
84
  return
85
85
 
@@ -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,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
- }