├── LICENSE ├── .github └── workflows │ ├── npm.yml │ └── test.yml ├── example.html ├── surreal.js └── README.md /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. 20 | -------------------------------------------------------------------------------- /.github/workflows/npm.yml: -------------------------------------------------------------------------------- 1 | name: Publish to npm 2 | 3 | on: 4 | push: 5 | tags: 6 | - '*' # Matches all tags like 1.2.3 7 | workflow_dispatch: 8 | 9 | jobs: 10 | publish: 11 | runs-on: ubuntu-latest 12 | 13 | steps: 14 | - name: Checkout repository 15 | uses: actions/checkout@v4 16 | 17 | - name: Set up Node.js 18 | uses: actions/setup-node@v4 19 | with: 20 | node-version: '20' 21 | registry-url: 'https://registry.npmjs.org/' 22 | 23 | - name: Extract version from tag 24 | id: get_version 25 | run: echo "VERSION=${GITHUB_REF#refs/tags/}" >> "$GITHUB_OUTPUT" 26 | 27 | - name: Generate package.json 28 | run: | 29 | cat > package.json <> surreal.js 23 | if (typeof window !== 'undefined') { window.surreal = surreal; } else if (typeof globalThis !== 'undefined') { globalThis.surreal = surreal; } 24 | EOF 25 | 26 | - name: Setup Node.js 27 | uses: actions/setup-node@v3 28 | with: 29 | node-version: '18' 30 | 31 | - name: Install dependencies and configure ESM 32 | run: | 33 | npm init -y 34 | jq '. + {type: "module"}' package.json > tmp.json && mv tmp.json package.json 35 | npm install mocha chai jsdom jsdom-global 36 | 37 | - name: Create test file 38 | run: | 39 | mkdir -p test 40 | cat << 'EOF' > test/surreal.test.js 41 | import 'jsdom-global/register.js'; 42 | import { expect } from 'chai'; 43 | 44 | // Load surreal.js (adds Surreal to window) 45 | import '../surreal.js'; 46 | 47 | describe('Surreal Test Suite', () => { 48 | it('Test suite works?', () => { 49 | expect(1 + 1).to.equal(2); 50 | }); 51 | 52 | it('should have surreal defined globally on window', () => { 53 | expect(window.surreal).to.exist; 54 | }); 55 | 56 | it('should have a method classRemove if exists', () => { 57 | expect(window.surreal.classRemove).to.be.a('function'); 58 | }); 59 | 60 | it('classAdd should add class to element', () => { 61 | expect(window.surreal.classAdd).to.be.a('function'); 62 | 63 | const el = document.createElement('div'); 64 | window.surreal.classAdd(el, 'test-class'); 65 | 66 | expect(el.classList.contains('test-class')).to.be.true; 67 | }); 68 | }); 69 | EOF 70 | 71 | - name: Run tests 72 | run: npx mocha test/surreal.test.js 73 | -------------------------------------------------------------------------------- /example.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Quick and Dirty Testing for Surreal 5 | 6 | 7 | 8 | 9 | 10 | 11 | 16 | 24 |
I should be red. 25 | 28 |
29 | 30 |
I should turn grey after 2 seconds. 31 | 39 |
40 |
I should be animated using events. 41 | 57 |
58 |
I should be animated using timeline / async until finished! 59 | 81 |
82 |
I should be surrounded by diamonds after a few seconds. 83 | 106 |
107 | 108 |
📬 I close from a child button event. 109 | 112 | 117 |
118 |
📭 I stay open because of halt() 119 | 122 |
📬 I close dramatically from a child button async event 123 | 138 | 143 |
144 |
145 | 146 |
147 | 3D Card 148 |
149 | 173 |
174 | 175 | 176 | 177 | 178 | 183 | 184 | 304 | 305 | 306 | -------------------------------------------------------------------------------- /surreal.js: -------------------------------------------------------------------------------- 1 | // Welcome to Surreal 1.3.4 2 | // Documentation: https://github.com/gnat/surreal 3 | // Locality of Behavior (LoB): https://htmx.org/essays/locality-of-behaviour/ 4 | let surreal = (function () { 5 | let $ = { // Convenience for internals. 6 | $: this, // Convenience for internals. 7 | plugins: [], 8 | 9 | // Table of contents and convenient call chaining sugar. For a familiar "jQuery like" syntax. 🙂 10 | // Check before adding new: https://youmightnotneedjquery.com/ 11 | sugar(e) { 12 | if (!$.isNode(e) && !$.isNodeList(e)) { console.warn(`Surreal: Not a supported element / node / node list "${e}"`); return e } 13 | if ($.isNodeList(e)) e.forEach(_ => { $.sugar(_) }) // Add Surreal to all nodes from any() 14 | if (e.hasOwnProperty('hasSurreal')) return e // Surreal already added. 15 | 16 | // General 17 | e.run = (f) => { return $.run(e, f) } 18 | e.remove = () => { return $.remove(e) } 19 | 20 | // Classes and CSS. 21 | e.classAdd = (name) => { return $.classAdd(e, name) } 22 | e.class_add = e.add_class = e.addClass = e.classAdd // Alias 23 | e.classRemove = (name) => { return $.classRemove(e, name) } 24 | e.class_remove = e.remove_class = e.removeClass = e.classRemove // Alias 25 | e.classToggle = (name, force) => { return $.classToggle(e, name, force) } 26 | e.class_toggle = e.toggle_class = e.toggleClass = e.classToggle // Alias 27 | e.styles = (value) => { return $.styles(e, value) } 28 | 29 | // Events. 30 | e.on = (name, f) => { return $.on(e, name, f) } 31 | e.off = (name, f) => { return $.off(e, name, f) } 32 | e.offAll = (name) => { return $.offAll(e, name) } 33 | e.off_all = e.offAll // Alias 34 | e.disable = () => { return $.disable(e) } 35 | e.enable = () => { return $.enable(e) } 36 | e.send = (name, detail) => { return $.send(e, name, detail) } 37 | e.trigger = e.send // Alias 38 | e.halt = (ev, keepBubbling, keepDefault) => { return $.halt(ev, keepBubbling, keepDefault) } 39 | 40 | // Attributes. 41 | e.attribute = (name, value) => { return $.attribute(e, name, value) } 42 | e.attributes = e.attr = e.attribute // Alias 43 | 44 | // Add all plugins. 45 | $.plugins.forEach(function(func) { func(e) }) 46 | 47 | e.hasSurreal = 1 48 | return e 49 | }, 50 | // me() will return a single element or null. Selector not needed if used with inline 56 | // 57 | me(selector=null, start=document, warning=true) { 58 | if (selector == null) return $.sugar(start.currentScript.parentElement) // Just local me() in 304 | // Example: 305 | const addOnload = (f) => { 306 | if (typeof window.onload === 'function') { // window.onload already is set, queue functions together (creates a call chain). 307 | let onload_old = window.onload 308 | window.onload = () => { 309 | onload_old() 310 | f() 311 | } 312 | return 313 | } 314 | window.onload = f // window.onload was not set yet. 315 | } 316 | const onloadAdd = addOnload; const onload_add = addOnload; const add_onload = addOnload 317 | console.log("Surreal: Added shortcuts.") 318 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 🗿 Surreal 2 | ### Tiny jQuery alternative for plain Javascript with inline [Locality of Behavior](https://htmx.org/essays/locality-of-behaviour/)! 3 | 4 | ![cover](https://user-images.githubusercontent.com/24665/171092805-b41286b2-be4a-4aab-9ee6-d604699cc507.png) 5 | (Art by [shahabalizadeh](https://www.deviantart.com/shahabalizadeh)) 6 | 12 | 13 | ## Why does this exist? 14 | 15 | For devs who love ergonomics! You may appreciate Surreal if: 16 | 17 | * You want to stay as close as possible to Vanilla JS. 18 | * Hate typing `document.querySelector` over.. and over.. 19 | * Hate typing `addEventListener` over.. and over.. 20 | * Really wish `document.querySelectorAll` had Array functions.. 21 | * Really wish `this` would work in any inline ` 62 | 63 | ``` 64 | 65 | See the [Live Example](https://gnat.github.io/surreal/example.html)! Then [view source](https://github.com/gnat/surreal/blob/main/example.html). 66 | 67 | ## 🎁 Install 68 | 69 | Surreal is only 320 lines. No build step. No dependencies. 70 | 71 | [📥 Download](https://raw.githubusercontent.com/gnat/surreal/main/surreal.js) into your project, and add `` in your `` 72 | 73 | Or, 🌐 via CDN: `` 74 | 75 | ## ⚡ Usage 76 | 77 | ### 🔍️ DOM Selection 78 | 79 | * Select **one** element: `me(...)` 80 | * Can be any of: 81 | * CSS selector: `".button"`, `"#header"`, `"h1"`, `"body > .block"` 82 | * Variables: `body`, `e`, `some_element` 83 | * Events: `event.currentTarget` will be used. 84 | * Surreal selectors: `me()`,`any()` 85 | * Choose the start location in the DOM with the 2nd arg. (Default: `document`) 86 | * 🔥 `any('button', me('#header')).classAdd('red')` 87 | * Add `.red` to any `