@daz4126/helium 0.3.0 → 0.4.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.
Files changed (3) hide show
  1. package/README.md +104 -12
  2. package/helium.js +56 -18
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -2,14 +2,16 @@
2
2
 
3
3
  The ultra-light library that makes HTML interactive!
4
4
 
5
- Here's a simple example of a button that counts clicks and turns red after more than 3 presses:
5
+ Here's a simple example of a button that counts the number of times it has been clicked and turns red after more than 3 clicks:
6
6
 
7
7
  ```html
8
- <button @helium @click="count++" :style="count > 3 && 'background: red'">
9
- clicked <span @react="count">0</span> times
8
+ <button @click="count++" :style="count > 3 && 'background: red'">
9
+ clicked <b @text="count">0</b> times
10
10
  </button>
11
11
  ```
12
12
 
13
+ It's really simple to use - just sprinkle the magic @attributes into your HTML and watch it come alive!
14
+
13
15
  [See more examples here](https://codepen.io/daz4126/pen/YPwwdBK)
14
16
 
15
17
  To use, just import from the CDN then call the `helium` funtion (no install or build step required!):
@@ -29,32 +31,36 @@ Then include it in your
29
31
 
30
32
  ```javascript
31
33
  import helium from "@daz4126/helium"
32
- helium
34
+ helium()
33
35
  ```
34
36
 
37
+ # Helium Attributes
38
+
39
+ Helium uses custom attributes to add interactivity to HTML elements. To identify them, they all start with `@`, although there are also `data` attribute aliases that can be used instead.
40
+
35
41
  ## `@helium`
36
42
 
37
43
  This attribute sets the root element. Helium attributes can only be used on this element and its children. If not set then it defaults to `document`.
38
44
 
39
45
  Alias: `data-helium`
40
46
 
41
- ## `@react`
47
+ ## `@text`
42
48
 
43
49
  Inserts the result of a JavaScript expression into the text-content of the element.
44
50
 
45
51
  This will update the textContent of the element with the value of the `count` variable:
46
52
 
47
53
  ```html
48
- <b @react="count">0</b>
54
+ <b @text="count">0</b>
49
55
  ```
50
56
 
51
57
  You can also use expresions. This will update the textContent of the element with the value of the `name` variable but in uppercase:
52
58
 
53
59
  ```html
54
- <span @react="name.toUpperCase()">0</span>
60
+ <span @text="name.toUpperCase()">0</span>
55
61
  ```
56
62
 
57
- Alias: `data-he-react`
63
+ Alias: `data-he-text`
58
64
 
59
65
  ## `@bind`
60
66
 
@@ -73,7 +79,7 @@ Alias: `data-he-bind`
73
79
  Makes the element hidden or visible depending on the result of a JavaScript expression.
74
80
 
75
81
  ```html
76
- <div @hidden="count > 3">Only visible if the count is greater than 3</div>
82
+ <div @visible="count > 3">Only visible if the count is greater than 3</div>
77
83
  ```
78
84
 
79
85
  Alias: `data-he-hidden` & `data-he-visible`
@@ -132,16 +138,102 @@ You can add modifiers of `prevent` to prevent the default behaviour, `outside` t
132
138
 
133
139
  Alias: prepend the event name with `data-he-on`, for example `data-he-onclick="count++"`
134
140
 
135
- ## Conditional Attributes
141
+ ## Dynamic Attributes
142
+
143
+ It's possible to dynamically update the attributes of elements. To do this, just prepend a `:` in front of the attribute name and write a JavaScript expression that evaulates to the desired attribute value. This will update whenever any of the Helium variables change value.
144
+
145
+ In the following example, the `<div>` element has a dynamic class attribute that will be 'normal' if the count is less than 10, but 'danger' if the count is 10 or more:
146
+
147
+ ```html
148
+ <div :class="count < 10 ? 'normal' : 'danger'>
149
+ The count is <b @text=count></b>
150
+ </div>
151
+ ```
136
152
 
137
153
  ## Magic Attributes
138
154
 
139
155
  `$` is an alias for `document.querySelector`
140
156
 
141
- `$el` is an alias for the element
142
157
 
143
- `$event` is an alias for the event object of an event handler
158
+ ```html
159
+ <div @click="$('#header').classList.add('active')">Activate Header!</div>
160
+ ```
161
+
162
+ `$el` is an alias for the element:
163
+
164
+ ```html
165
+ <div @click="$el.remove()">Click to remove me!</div>
166
+ ```
167
+
168
+ `$event` is an alias for the event object of an event handler:
169
+
170
+
171
+ ```html
172
+ <div @click="console.log($event.timeStamp)">Log the timestamp</div>
173
+ ```
174
+
175
+ `@data` gives you access to the data object that contains all the Helium variables:
176
+
177
+
178
+ ```html
179
+ <div @click="console.log($data)">Log the data</div>
180
+ ```
144
181
 
145
182
  ## Default Variables and Functions
146
183
 
147
184
  The helium function accepts a single JavaScript object as an argument. This can include default variable values and functions that can then be called inside the JavaScript expressions.
185
+
186
+ For example, the following will set the `count` variable to an initial value of `29` and the `name` variable to "Helium":
187
+
188
+ ```javascript
189
+ helium({ count: 29, name: "Helium"})
190
+ ```
191
+
192
+ The following example shows how a function can be added into Helium and then used by even listeners:
193
+
194
+ ```javascript
195
+ helium({
196
+ appendTo(element){
197
+ const li = document.createElement("li")
198
+ li.textContent = "New Item"
199
+ element.append(li)
200
+ }
201
+ })
202
+ ```
203
+
204
+ This function can then be called from an event handler, such as `@click`:
205
+
206
+ ```html
207
+ <ul @ref="list"></ul>
208
+ <button @click="appendTo($list)>Append item to list</button>
209
+ ```
210
+
211
+ Note that magic attributes and Helium variables are not available inside these functions. However, you can pass them as arguments and they will then be available in the function. If you pass Helium variables then they will be passed as values and updating them inside the function will *not* trigger a reactive update. A solution is to pass the magic `$data` attribute as an argument, then updating the properties of this inside the function *will* trigger a reactive update.
212
+
213
+ So instead of this:
214
+
215
+ ```html
216
+ <button @click="increment(count)">Increment Count</button>
217
+ ```
218
+
219
+ ```javascript
220
+ helium({
221
+ increment(count,n = 1){
222
+ count += n
223
+ }
224
+ })
225
+ ```
226
+
227
+ You should do this instead:
228
+
229
+ ```html
230
+ <button @click="increment($data)">Increment Count</button>
231
+ ```
232
+
233
+ ```javascript
234
+ helium({
235
+ increment(data,n = 1){
236
+ data.count += n
237
+ }
238
+ })
239
+ ```
package/helium.js CHANGED
@@ -33,12 +33,12 @@ export default function helium(data = {}) {
33
33
  try {
34
34
  return new Function(
35
35
  "$",
36
- "$state",
36
+ "$data",
37
37
  "$event",
38
38
  "$el",
39
39
  ...Object.keys(data),
40
40
  ...[...refs.keys()],
41
- `with($state) { ${withReturn ? "return" : ""} (${expr.trim()}) }`,
41
+ `with($data) { ${withReturn ? "return" : ""} (${expr.trim()}) }`,
42
42
  );
43
43
  } catch (err) {
44
44
  return () => expr;
@@ -65,8 +65,6 @@ export default function helium(data = {}) {
65
65
  heliumElements.forEach((el) => {
66
66
  for (const { name, value } of el.attributes) {
67
67
  if (
68
- name == "@react" ||
69
- name == "data-he-react" ||
70
68
  name == "@text" ||
71
69
  name == "data-he-text" ||
72
70
  name == "@bind" ||
@@ -75,7 +73,7 @@ export default function helium(data = {}) {
75
73
  try {
76
74
  new Function(`let ${value} = 1`);
77
75
  state[value] ||=
78
- name == "@react" || name == "data-he-react"
76
+ name == "@text" || name == "data-he-text"
79
77
  ? el.textContent
80
78
  : el.value;
81
79
  } catch (e) {}
@@ -97,12 +95,7 @@ export default function helium(data = {}) {
97
95
  ),
98
96
  );
99
97
  if (name == "@ref" || name == "data-he-ref") refs.set("$" + value, el);
100
- if (
101
- name == "@react" ||
102
- name == "data-he-react" ||
103
- name == "@text" ||
104
- name == "data-he-text"
105
- )
98
+ if (name == "@text" || name == "data-he-text")
106
99
  Object.keys(state)
107
100
  .filter((key) => value.includes(key))
108
101
  .forEach((val) =>
@@ -178,10 +171,50 @@ export default function helium(data = {}) {
178
171
  const [eventName, ...modifiers] = name
179
172
  .slice(name.startsWith("@") ? 1 : 10)
180
173
  .split(".");
181
- const receiver = modifiers.includes("outside") ? document : el;
182
- receiver.addEventListener(eventName, function _handler(e) {
174
+ const receiver =
175
+ modifiers.includes("outside") || modifiers.includes("document")
176
+ ? document
177
+ : el;
178
+ const debounce = (fn, delay) => {
179
+ let timeout;
180
+ return function (...args) {
181
+ clearTimeout(timeout);
182
+ timeout = setTimeout(() => fn.apply(this, args), delay);
183
+ };
184
+ };
185
+
186
+ let debounceDelay = 0;
187
+ const debounceModifier = modifiers.find((m) =>
188
+ m.startsWith("debounce"),
189
+ );
190
+ if (debounceModifier) {
191
+ const time = debounceModifier.split(":")[1];
192
+ if (time && !isNaN(time)) debounceDelay = Number(time);
193
+ else debounceDelay = 300;
194
+ }
195
+
196
+ function _handler(e) {
183
197
  if (modifiers.includes("prevent")) e.preventDefault();
184
- if (!modifiers.includes("outside") || !el.contains(e.target))
198
+ const keyModifiers = {
199
+ shift: "shiftKey",
200
+ ctrl: "ctrlKey",
201
+ alt: "altKey",
202
+ meta: "metaKey",
203
+ };
204
+ for (const [mod, prop] of Object.entries(keyModifiers)) {
205
+ if (modifiers.includes(mod) && !e[prop]) return;
206
+ }
207
+ if (["keydown", "keyup", "keypress"].includes(eventName)) {
208
+ const last = modifiers[modifiers.length - 1];
209
+ if (last) {
210
+ let keyName = e.key;
211
+ if (keyName === " ") keyName = "Space";
212
+ else if (keyName === "Escape") keyName = "Esc";
213
+ if (keyName.toLowerCase() !== last.toLowerCase()) return;
214
+ }
215
+ }
216
+
217
+ if (!modifiers.includes("outside") || !el.contains(e.target)) {
185
218
  compileExpression(value, false)(
186
219
  $,
187
220
  state,
@@ -190,9 +223,15 @@ export default function helium(data = {}) {
190
223
  ...Object.values(data),
191
224
  ...[...refs.values()],
192
225
  );
226
+ }
193
227
  if (modifiers.includes("once"))
194
- el.removeEventListener(eventName, _handler);
195
- });
228
+ el.removeEventListener(eventName, debouncedHandler);
229
+ }
230
+
231
+ const debouncedHandler =
232
+ debounceDelay > 0 ? debounce(_handler, debounceDelay) : _handler;
233
+
234
+ receiver.addEventListener(eventName, debouncedHandler);
196
235
  }
197
236
  }
198
237
  });
@@ -204,8 +243,7 @@ export default function helium(data = {}) {
204
243
  }
205
244
  });
206
245
  observer.observe(element, { childList: true, subtree: true });
207
- for (const [key, items] of bindings.entries())
208
- items.forEach((binding) => applyBinding(binding));
246
+ for (const [key, items] of bindings.entries()) items.forEach(applyBinding);
209
247
  }
210
248
  processElements(root);
211
249
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@daz4126/helium",
3
- "version": "0.3.0",
3
+ "version": "0.4.0",
4
4
  "main": "helium.js",
5
5
  "type": "module",
6
6
  "keywords": [