@panphora/clayjs 0.1.0 → 0.2.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.
Files changed (45) hide show
  1. package/NOTICE +19 -0
  2. package/README.md +2 -0
  3. package/_headers +2 -0
  4. package/all.js +24 -0
  5. package/clay-data.js +16 -0
  6. package/clay-dom.js +18 -0
  7. package/clay-events.js +28 -0
  8. package/clay-options.js +27 -0
  9. package/clay-ui.js +29 -0
  10. package/clay-utils.js +18 -0
  11. package/clay.js +17 -5
  12. package/package.json +25 -2
  13. package/sap.js +16 -0
  14. package/src/core/save.js +5 -4
  15. package/src/core/snapshot.js +12 -9
  16. package/src/dom/all.js +440 -0
  17. package/src/dom/dom-helpers.js +218 -0
  18. package/src/dom/form-data.js +62 -0
  19. package/src/dom/insert-style-tag.js +109 -0
  20. package/src/dom/nearest.js +97 -0
  21. package/src/dom/pipe.js +1 -0
  22. package/src/events/index.js +22 -0
  23. package/src/events/onclickaway.js +27 -0
  24. package/src/events/onclickchildren.js +12 -0
  25. package/src/events/onclone.js +54 -0
  26. package/src/events/onmutation.js +54 -0
  27. package/src/events/onpagemutation.js +37 -0
  28. package/src/events/onrender.js +32 -0
  29. package/src/lib/mutation.js +26 -9
  30. package/src/lib/on-load.js +9 -0
  31. package/src/loader-logic.js +2 -1
  32. package/src/loader.js +2 -0
  33. package/src/options/options.js +253 -0
  34. package/src/plugins/demo.js +124 -0
  35. package/src/sync/live-sync.js +61 -19
  36. package/src/sync/merge-tags.js +36 -0
  37. package/src/ui/dialogs.js +206 -0
  38. package/src/ui/index.js +38 -0
  39. package/src/ui/modal.js +884 -0
  40. package/src/ui/toast.js +434 -0
  41. package/src/utils/copy-to-clipboard.js +27 -0
  42. package/src/utils/debounce.js +23 -0
  43. package/src/utils/index.js +10 -0
  44. package/src/utils/slugify.js +20 -0
  45. package/src/vendor/hyper-morph.vendor.js +2 -1
package/src/dom/all.js ADDED
@@ -0,0 +1,440 @@
1
+ // Helper to check if an array contains DOM nodes
2
+ const isElementArray = (arr) => {
3
+ if (!Array.isArray(arr)) return false;
4
+ // Empty arrays from element operations should stay wrapped for chainability
5
+ if (arr.length === 0) return true;
6
+ return arr.every(item => item instanceof Element || item instanceof Node || item instanceof Document);
7
+ };
8
+
9
+ const createMethodHandler = (elements, plugins, methods) => ({
10
+ get(target, prop) {
11
+ // Handle array-like numeric access and length
12
+ if (prop === 'length') return elements.length;
13
+
14
+ // Handle Symbol.iterator BEFORE number check
15
+ if (prop === Symbol.iterator) {
16
+ return elements[Symbol.iterator].bind(elements);
17
+ }
18
+
19
+ // Handle other Symbols (toStringTag, etc.) - return undefined to avoid Symbol-to-number conversion
20
+ if (typeof prop === 'symbol') {
21
+ return undefined;
22
+ }
23
+
24
+ // Handle numeric indices
25
+ if (!isNaN(prop)) return elements[prop];
26
+
27
+ // Handle array methods
28
+ if (prop in Array.prototype) {
29
+ const arrayMethod = Array.prototype[prop];
30
+ return (...args) => {
31
+ const result = arrayMethod.apply(elements, args);
32
+ // For methods that return arrays, only wrap if they contain DOM nodes
33
+ // Otherwise return plain array (jQuery-like behavior for primitives)
34
+ if (Array.isArray(result)) {
35
+ return isElementArray(result)
36
+ ? createElementProxy(result, plugins, methods)
37
+ : result; // Return plain array for primitives
38
+ }
39
+ // For single element returns (like find), return raw value
40
+ if (result instanceof Element) {
41
+ return result;
42
+ }
43
+ // For boolean/primitive returns (some, every, etc), return raw value
44
+ return result;
45
+ };
46
+ }
47
+
48
+ // Handle event listeners and support event delegation
49
+ if (prop.startsWith('on')) {
50
+ return (selectorOrHandlerOrObject, optionalHandler) => {
51
+ const eventName = prop.slice(2);
52
+
53
+ // Handle object of bindings
54
+ if (selectorOrHandlerOrObject && typeof selectorOrHandlerOrObject === 'object') {
55
+ Object.entries(selectorOrHandlerOrObject).forEach(([selector, handler]) => {
56
+ elements.forEach(el => {
57
+ el.addEventListener(eventName, (e) => {
58
+ const closest = e.target.closest(selector);
59
+ if (closest && el.contains(closest)) {
60
+ handler.call(closest, e);
61
+ }
62
+ });
63
+ });
64
+ });
65
+ return createElementProxy(elements, plugins, methods);
66
+ }
67
+
68
+ // Handle single binding (existing delegation code)
69
+ if (optionalHandler) {
70
+ const selector = selectorOrHandlerOrObject;
71
+ const handler = optionalHandler;
72
+
73
+ elements.forEach(el => {
74
+ el.addEventListener(eventName, (e) => {
75
+ const closest = e.target.closest(selector);
76
+ if (closest && el.contains(closest)) {
77
+ handler.call(closest, e);
78
+ }
79
+ });
80
+ });
81
+ } else {
82
+ const handler = selectorOrHandlerOrObject;
83
+ elements.forEach(el => el.addEventListener(eventName, handler));
84
+ }
85
+
86
+ return createElementProxy(elements, plugins, methods);
87
+ };
88
+ }
89
+
90
+ // Check plugins first
91
+ if (plugins[prop]) {
92
+ const plugin = plugins[prop];
93
+ if (typeof plugin === 'function') {
94
+ return createElementProxy(plugin(elements), plugins, methods);
95
+ }
96
+ if (typeof plugin === 'object') {
97
+ if (plugin.value) {
98
+ return createElementProxy(plugin.value(elements), plugins, methods);
99
+ }
100
+ if (plugin.properties) {
101
+ return createNestedPluginProxy(elements, plugin.properties, plugins, methods);
102
+ }
103
+ }
104
+ return plugin;
105
+ }
106
+
107
+ if (methods[prop]) {
108
+ const method = methods[prop];
109
+ return (...args) => {
110
+ const result = method.apply(createElementProxy(elements, plugins, methods), args);
111
+ // Only wrap arrays that contain DOM nodes, return plain arrays for primitives
112
+ return (result instanceof Array && isElementArray(result))
113
+ ? createElementProxy(result, plugins, methods)
114
+ : result;
115
+ };
116
+ }
117
+
118
+ // Check if we have any elements to work with
119
+ // Example: All('.missing') returns [] so firstEl is null
120
+ const firstEl = elements[0];
121
+ if (!firstEl) {
122
+ // No elements found - need to handle property access gracefully
123
+ // so chains like All.missing.classList.add('hidden') don't break
124
+
125
+ // These properties return objects with their own methods (classList.add, style.color, etc)
126
+ // We know these are safe to proxy even when empty
127
+ if (['style', 'classList', 'dataset'].includes(prop)) {
128
+ return createIntermediateProxy([], prop, plugins, methods);
129
+ }
130
+
131
+ // For unknown properties, we need to check what they are on the DOM
132
+ // BUT we can't do Element.prototype.classList - that throws "Illegal invocation"
133
+ // Instead we use getOwnPropertyDescriptor which safely tells us ABOUT the property
134
+ const descriptor = Object.getOwnPropertyDescriptor(Element.prototype, prop) ||
135
+ Object.getOwnPropertyDescriptor(Node.prototype, prop);
136
+
137
+ if (descriptor?.get) {
138
+ // Properties like 'children', 'parentElement' have getters
139
+ // Treat them like classList - return a proxy that won't break chains
140
+ return createIntermediateProxy([], prop, plugins, methods);
141
+ }
142
+
143
+ if (typeof descriptor?.value === 'function') {
144
+ // Regular methods like 'appendChild', 'remove'
145
+ // Return a no-op function: All.missing.remove() does nothing, returns proxy
146
+ return (...args) => createElementProxy([], plugins, methods);
147
+ }
148
+
149
+ // Property doesn't exist on DOM elements (like All.missing.foobar)
150
+ return undefined;
151
+ }
152
+
153
+ const value = firstEl[prop];
154
+
155
+ // The library is designed to handle chaining correctly
156
+ // - When a method returns an Element (like cloneNode), it wraps it in a proxy
157
+ // - When a method returns undefined (like removeAttribute), it chains on the original elements
158
+ // - For other return values, like strings, it returns the results in an array
159
+ // We also handle passing in an all-wrapped proxy object as an argument and loop over all elements in it
160
+ // In the method handler's get function, replace the function call handling with:
161
+ if (typeof value === 'function') {
162
+ return (...args) => {
163
+ // Unwrap any proxy arguments
164
+ const unwrappedArgs = args.map(arg => {
165
+ if (arg && arg.constructor === Proxy) {
166
+ return Array.from(arg);
167
+ }
168
+ return arg;
169
+ });
170
+
171
+ const results = elements.map(el => {
172
+ // Check if any of the unwrapped arguments are arrays (from proxies)
173
+ const hasProxyArgs = unwrappedArgs.some(Array.isArray);
174
+
175
+ if (hasProxyArgs) {
176
+ // Handle proxy arguments case
177
+ const elementResults = unwrappedArgs.map(arg => {
178
+ if (Array.isArray(arg)) {
179
+ return arg.map(proxyEl => el[prop](proxyEl));
180
+ }
181
+ return [el[prop](...args)];
182
+ }).flat();
183
+ return elementResults[elementResults.length - 1];
184
+ } else {
185
+ // Simple case - just call the method once with original arguments
186
+ return el[prop](...args);
187
+ }
188
+ });
189
+
190
+ if (results[0] instanceof Element) {
191
+ return createElementProxy(results.filter(Boolean), plugins, methods);
192
+ }
193
+ if (results[0] === undefined) {
194
+ return createElementProxy(elements, plugins, methods);
195
+ }
196
+ return results;
197
+ };
198
+ }
199
+
200
+ // Handle intermediate objects (style, classList, dataset)
201
+ if (['style', 'classList', 'dataset'].includes(prop)) {
202
+ return createIntermediateProxy(elements, prop, plugins, methods);
203
+ }
204
+
205
+ // Return array of values for leaf properties
206
+ return elements.map(el => el[prop]);
207
+ },
208
+
209
+ set(target, prop, value) {
210
+ if (typeof value === 'function') {
211
+ elements.forEach(el => {
212
+ el[prop] = value(el);
213
+ });
214
+ } else {
215
+ elements.forEach(el => {
216
+ el[prop] = value;
217
+ });
218
+ }
219
+ return true;
220
+ }
221
+ });
222
+
223
+ const createNestedPluginProxy = (elements, properties, plugins, methods) => {
224
+ return new Proxy({}, {
225
+ get(target, prop) {
226
+ if (properties[prop]) {
227
+ const plugin = properties[prop];
228
+ if (typeof plugin === 'function') {
229
+ return createElementProxy(plugin(elements), plugins, methods);
230
+ }
231
+ return plugin;
232
+ }
233
+ return undefined;
234
+ }
235
+ });
236
+ };
237
+
238
+ const createIntermediateProxy = (elements, propName, plugins, methods) => {
239
+ return new Proxy({}, {
240
+ get(target, prop) {
241
+ // Handle function properties (like classList.add)
242
+ const firstEl = elements[0];
243
+ if (!firstEl) {
244
+ // Return no-op function for any property access on empty collection
245
+ // This ensures chaining continues to work even with no elements
246
+ return (...args) => createElementProxy(elements, plugins, methods);
247
+ }
248
+
249
+ const value = firstEl[propName][prop];
250
+ if (typeof value === 'function') {
251
+ return (...args) => {
252
+ elements.forEach(el => el[propName][prop](...args));
253
+ return createElementProxy(elements, plugins, methods);
254
+ };
255
+ }
256
+
257
+ // Return array of values for leaf properties
258
+ return elements.map(el => el[propName][prop]);
259
+ },
260
+
261
+ set(target, prop, value) {
262
+ if (typeof value === 'function') {
263
+ elements.forEach(el => {
264
+ el[propName][prop] = value(el);
265
+ });
266
+ } else {
267
+ elements.forEach(el => {
268
+ el[propName][prop] = value;
269
+ });
270
+ }
271
+ return true;
272
+ }
273
+ });
274
+ };
275
+
276
+ const sharedState = {
277
+ plugins: {},
278
+ methods: {}
279
+ };
280
+
281
+ const createElementProxy = (elements, plugins = sharedState.plugins, methods = sharedState.methods) => {
282
+ return new Proxy(elements, createMethodHandler(elements, plugins, methods));
283
+ };
284
+
285
+ const toElementArray = (input) => {
286
+ if (input instanceof Element || input instanceof Document) {
287
+ return [input];
288
+ }
289
+ if (Array.isArray(input)) {
290
+ if (input.every(el => el instanceof Element || el instanceof Document)) {
291
+ return input;
292
+ }
293
+ throw new TypeError('All array elements must be DOM Elements or Document');
294
+ }
295
+ if (typeof input === 'string') {
296
+ return Array.from(document.querySelectorAll(input));
297
+ }
298
+ throw new TypeError('Input must be a selector string, Element, Document, or Array of Elements');
299
+ };
300
+
301
+ // Default plugins
302
+ const defaultPlugins = {
303
+ methods: {
304
+ eq(index) {
305
+ if (typeof index !== 'number') {
306
+ throw new TypeError('eq() requires a number as an argument');
307
+ }
308
+
309
+ // Handle negative indices (counting from the end)
310
+ const normalizedIndex = index < 0 ? this.length + index : index;
311
+
312
+ // Return array with single element at index, or empty array if index is invalid
313
+ // ❗️ Allows chaining to continue
314
+ return this[normalizedIndex] ? [this[normalizedIndex]] : [];
315
+ },
316
+
317
+ at(index) {
318
+ if (typeof index !== 'number') {
319
+ throw new TypeError('at() requires a number as an argument');
320
+ }
321
+ const normalizedIndex = index < 0 ? this.length + index : index;
322
+ return this[normalizedIndex];
323
+ },
324
+
325
+ prop(properties) {
326
+ if (typeof properties !== 'object' || properties === null) {
327
+ throw new TypeError('prop() requires an object of properties');
328
+ }
329
+
330
+ Object.entries(properties).forEach(([key, value]) => {
331
+ this.forEach(el => {
332
+ el[key] = value;
333
+ });
334
+ });
335
+
336
+ return this;
337
+ },
338
+
339
+ css(styles) {
340
+ if (typeof styles !== 'object' || styles === null) {
341
+ throw new TypeError('css() requires an object of styles');
342
+ }
343
+
344
+ Object.entries(styles).forEach(([property, value]) => {
345
+ this.forEach(el => {
346
+ el.style[property] = value;
347
+ });
348
+ });
349
+
350
+ return this;
351
+ },
352
+
353
+ pluck(attr) {
354
+ return this.map(el => el.getAttribute(attr));
355
+ },
356
+
357
+ unique() {
358
+ return [...new Set(this)];
359
+ },
360
+
361
+ sortBy(fn) {
362
+ if (typeof fn === 'string') {
363
+ const attr = fn;
364
+ fn = el => el.getAttribute(attr);
365
+ }
366
+ return [...this].sort((a, b) => {
367
+ const aVal = fn(a);
368
+ const bVal = fn(b);
369
+ if (aVal < bVal) return -1;
370
+ if (aVal > bVal) return 1;
371
+ return 0;
372
+ });
373
+ }
374
+ }
375
+ };
376
+
377
+ const All = new Proxy(function (selectorOrElements, contextSelector) {
378
+ // If there's a context selector, search within that context
379
+ if (arguments.length === 2) {
380
+ // Support both string selectors and element references as context
381
+ let contextElements;
382
+ if (typeof contextSelector === 'string') {
383
+ contextElements = Array.from(document.querySelectorAll(contextSelector));
384
+ } else {
385
+ // Convert element/array/document to element array
386
+ contextElements = toElementArray(contextSelector);
387
+ }
388
+
389
+ // If first arg is a string selector, search within each context
390
+ if (typeof selectorOrElements === 'string') {
391
+ const elements = contextElements.flatMap(context => {
392
+ // Include context itself if it matches the selector
393
+ const matches = context.matches(selectorOrElements) ? [context] : [];
394
+ // Plus all descendants that match
395
+ const descendants = Array.from(context.querySelectorAll(selectorOrElements));
396
+ return [...matches, ...descendants];
397
+ });
398
+ return createElementProxy(elements);
399
+ }
400
+
401
+ // If first arg is elements, filter to those within context
402
+ const searchElements = toElementArray(selectorOrElements);
403
+ const elements = searchElements.filter(el =>
404
+ contextElements.some(context => context.contains(el))
405
+ );
406
+ return createElementProxy(elements);
407
+ }
408
+
409
+ // Single argument - normalize and return
410
+ const elements = toElementArray(selectorOrElements);
411
+ return createElementProxy(elements);
412
+ }, {
413
+ get(target, prop) {
414
+ if (prop === 'use') {
415
+ return function (plugin) {
416
+ if (!plugin || typeof plugin !== 'object') {
417
+ throw new TypeError('Plugin must be an object with "properties" or "methods"');
418
+ }
419
+ if (plugin.properties) {
420
+ Object.assign(sharedState.plugins, plugin.properties);
421
+ }
422
+ if (plugin.methods) {
423
+ Object.assign(sharedState.methods, plugin.methods);
424
+ }
425
+ return this;
426
+ };
427
+ }
428
+
429
+ if (prop === Symbol.iterator) return undefined;
430
+ if (prop in target) return target[prop];
431
+
432
+ const elements = Array.from(document.querySelectorAll(`[${prop}], .${prop}`));
433
+ return createElementProxy(elements);
434
+ }
435
+ });
436
+
437
+ // Install default plugins
438
+ All.use(defaultPlugins);
439
+
440
+ export default All;
@@ -0,0 +1,218 @@
1
+ import nearest from "./nearest.js";
2
+ import pipe from "./pipe.js";
3
+
4
+ function init () {
5
+
6
+ // Bail if already initialized
7
+ if (HTMLElement.prototype.hasOwnProperty('nearest')) {
8
+ return;
9
+ }
10
+
11
+ // elem.el.menu returns nearest element with a "menu" attribute (alias for elem.nearest.menu)
12
+ // elem.nearest.project returns nearest element with a "project" attribute
13
+ Object.defineProperty(HTMLElement.prototype, 'el', {
14
+ configurable: true,
15
+ get: function() {
16
+ let element = this;
17
+
18
+ const handler = {
19
+ get(target, prop) {
20
+ return nearest(element, `[${prop}], .${prop}`);
21
+ }
22
+ };
23
+
24
+ return new Proxy({}, handler);
25
+ }
26
+ });
27
+
28
+ Object.defineProperty(HTMLElement.prototype, 'nearest', {
29
+ configurable: true,
30
+ get: function() {
31
+ let element = this;
32
+
33
+ const handler = {
34
+ get(target, prop) {
35
+ return nearest(element, `[${prop}], .${prop}`);
36
+ }
37
+ };
38
+
39
+ return new Proxy({}, handler);
40
+ }
41
+ });
42
+
43
+ // elem.val.project returns the value of the nearest element with "project" attribute
44
+ // elem.val.project = "hello world" sets the value of the nearest element with "project" attribute
45
+ // For form elements (input/select/textarea), uses the value property; otherwise uses the attribute
46
+ Object.defineProperty(HTMLElement.prototype, 'val', {
47
+ configurable: true,
48
+ get: function() {
49
+ let element = this;
50
+
51
+ const isFormElement = (elem) =>
52
+ elem.tagName === 'INPUT' || elem.tagName === 'SELECT' || elem.tagName === 'TEXTAREA';
53
+
54
+ const handler = {
55
+ get(target, prop) {
56
+ return nearest(element, `[${prop}], .${prop}`, elem => {
57
+ if (isFormElement(elem)) {
58
+ return elem.value;
59
+ }
60
+ return elem.getAttribute(prop);
61
+ });
62
+ },
63
+ set(target, prop, value) {
64
+ const foundElem = nearest(element, `[${prop}], .${prop}`);
65
+
66
+ if (foundElem) {
67
+ if (isFormElement(foundElem)) {
68
+ foundElem.value = value;
69
+ } else {
70
+ foundElem.setAttribute(prop, value);
71
+ }
72
+ }
73
+
74
+ return true;
75
+ }
76
+ };
77
+
78
+ return new Proxy({}, handler);
79
+ }
80
+ });
81
+
82
+ // elem.text.project returns the innerText of the nearest element with the "project" attribute
83
+ // elem.text.project = "hello world" sets the innerText of the nearest element with the "project" attribute
84
+ Object.defineProperty(HTMLElement.prototype, 'text', {
85
+ configurable: true,
86
+ get: function() {
87
+ let element = this;
88
+
89
+ const handler = {
90
+ get(target, prop) {
91
+ return nearest(element, `[${prop}], .${prop}`, elem => elem.innerText);
92
+ },
93
+ set(target, prop, value) {
94
+ const foundElem = nearest(element, `[${prop}], .${prop}`);
95
+
96
+ if (foundElem) {
97
+ foundElem.innerText = value;
98
+ }
99
+
100
+ return true;
101
+ }
102
+ };
103
+
104
+ return new Proxy({}, handler);
105
+ }
106
+ });
107
+
108
+ // elem.exec.sync_out() executes the code in the nearest "sync_out" attribute, using elem as the `this`
109
+ Object.defineProperty(HTMLElement.prototype, 'exec', {
110
+ configurable: true,
111
+ get: function() {
112
+ let element = this;
113
+
114
+ const handler = {
115
+ get(target, prop) {
116
+ return function() {
117
+ const foundElem = nearest(element, `[${prop}], .${prop}`);
118
+ if (foundElem) {
119
+ const code = foundElem.getAttribute(prop);
120
+ if (code) {
121
+ return new Function(code).call(foundElem);
122
+ }
123
+ }
124
+ };
125
+ }
126
+ };
127
+
128
+ return new Proxy({}, handler);
129
+ }
130
+ });
131
+
132
+ /**
133
+ * CYCLE METHODS
134
+ *
135
+ * Element.prototype.cycle - Cycles through elements with a specific attribute and replaces the current element with the next one
136
+ * Usage: element.cycle(1, 'project') - Replaces element with the next element that has 'project' attribute
137
+ *
138
+ * Element.prototype.cycleAttr - Cycles through possible values of a specific attribute on matching elements
139
+ * Usage:
140
+ * element.cycleAttr(1, 'project_type') - Sets project_type to the next value found on elements with project_type
141
+ * element.cycleAttr(1, 'project_type', 'option:project_type') - Sets project_type based on values from option:project_type
142
+ *
143
+ * Notes:
144
+ * - Both methods support forward/backward cycling with positive/negative order parameter
145
+ * - For attributes containing special characters like colons (e.g., 'option:project_type'),
146
+ * the colon is automatically escaped for the CSS selector
147
+ * - cycle() replaces the entire element with the next one
148
+ * - cycleAttr() only changes the attribute value on the current element
149
+ */
150
+ Element.prototype.cycle = function(order = 1, attr) {
151
+ const escapedAttr = attr.replace(/:/g, '\\:');
152
+ const next = pipe(
153
+ // Get all elements
154
+ () => document.querySelectorAll(`[${escapedAttr}]`),
155
+
156
+ // Convert to array of {element, value} objects
157
+ els => Array.from(els).map(el => ({
158
+ element: el,
159
+ value: el.textContent.trim()
160
+ })),
161
+
162
+ // Get unique by value
163
+ els => [...new Map(els.map(el => [el.value, el])).values()],
164
+
165
+ // Sort by value
166
+ els => els.sort((a, b) => a.value.localeCompare(b.value)),
167
+
168
+ // Find next element object
169
+ els => els[(els.findIndex(el => el.value === this.textContent.trim()) + order + els.length) % els.length],
170
+
171
+ // Return just the element
172
+ obj => obj.element
173
+ )();
174
+
175
+ if (next) this.replaceWith(next.cloneNode(true));
176
+ };
177
+
178
+ Element.prototype.cycleAttr = function(order = 1, setAttr, lookupAttr) {
179
+ // If lookupAttr is not provided, use setAttr for both
180
+ lookupAttr = lookupAttr || setAttr;
181
+
182
+ const escapedLookupAttr = lookupAttr.replace(/:/g, '\\:');
183
+ const next = pipe(
184
+ // Get all elements that match the lookup attribute
185
+ () => document.querySelectorAll(`[${escapedLookupAttr}]`),
186
+
187
+ // Get unique attribute values
188
+ els => [...new Set(Array.from(els).map(el =>
189
+ el.getAttribute(lookupAttr)
190
+ ))],
191
+
192
+ // Sort them
193
+ vals => vals.sort(),
194
+
195
+ // Get next value based on current setAttr value
196
+ vals => vals[(vals.indexOf(this.getAttribute(setAttr)) + order + vals.length) % vals.length]
197
+ )();
198
+
199
+ if (next) this.setAttribute(setAttr, next);
200
+ };
201
+
202
+ Element.prototype.show = function() {
203
+ this.style.display = '';
204
+ return this;
205
+ };
206
+
207
+ Element.prototype.hide = function() {
208
+ this.style.display = 'none';
209
+ return this;
210
+ };
211
+
212
+ }
213
+
214
+ // Auto-init when module is imported
215
+ init();
216
+
217
+ export { init };
218
+ export default init;