├── .editorconfig ├── .gitignore ├── Hat.code-workspace ├── Hat.cover.ts ├── Hat.ts ├── LICENSE.txt ├── package.json ├── readme.md ├── tsconfig.json └── tsconfig.release.json /.editorconfig: -------------------------------------------------------------------------------- 1 | [*] 2 | indent_style = tab 3 | indent_size = 2 4 | end_of_line = lf 5 | insert_final_newline = true 6 | trim_trailing_whitespace = false 7 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | +* -------------------------------------------------------------------------------- /Hat.code-workspace: -------------------------------------------------------------------------------- 1 | { 2 | "folders": [ 3 | { 4 | "path": ".", 5 | }, 6 | ], 7 | "settings": { 8 | "files.exclude": { 9 | "**/.git": true, 10 | "**/.DS_Store": true, 11 | "**/node_modules": true, 12 | "**/package-lock.json": true, 13 | "*.tsbuildinfo": true, 14 | "*.d.ts.map": true, 15 | }, 16 | "search.exclude": { 17 | "**/.git": true, 18 | "**/.DS_Store": true, 19 | "**/build": true, 20 | "**/node_modules": true, 21 | "**/package-lock.json": true, 22 | "**/lib/*.js": true, 23 | "index.*": true 24 | }, 25 | "task.allowAutomaticTasks": "on", 26 | }, 27 | "launch": { 28 | "configurations": [ 29 | { 30 | "name": "Debug Active Cover Function", 31 | "type": "chrome", 32 | "request": "launch", 33 | "runtimeExecutable": "${workspaceRoot}/node_modules/.bin/electron", 34 | "cwd": "${workspaceRoot}", 35 | "runtimeArgs": [ 36 | "${workspaceRoot}/node_modules/moduless/build/moduless.js", 37 | "--remote-debugging-port=9222" 38 | ], 39 | "sourceMaps": true, 40 | "timeout": 2000 41 | }, 42 | { 43 | "name": "Debug All Cover Functions", 44 | "type": "chrome", 45 | "request": "launch", 46 | "runtimeExecutable": "${workspaceRoot}/node_modules/.bin/electron", 47 | "cwd": "${workspaceRoot}", 48 | "runtimeArgs": [ 49 | "${workspaceRoot}/node_modules/moduless/build/moduless.js", 50 | "--remote-debugging-port=9222", 51 | "expression=(cover)" 52 | ], 53 | "sourceMaps": true, 54 | "timeout": 2000 55 | } 56 | ] 57 | }, 58 | "tasks": { 59 | "version": "2.0.0", 60 | "tasks": [ 61 | { 62 | "label": "Compile Library", 63 | "type": "shell", 64 | "command": "tsc", 65 | "args": [ 66 | "--build", 67 | "--watch" 68 | ], 69 | "options": { 70 | "cwd": "${workspaceRoot}" 71 | }, 72 | "problemMatcher": [ 73 | "$tsc" 74 | ], 75 | "runOptions": { 76 | "runOn": "folderOpen" 77 | }, 78 | "group": { 79 | "kind": "build", 80 | "isDefault": true 81 | }, 82 | "isBackground": true 83 | }, 84 | { 85 | "label": "Set Active Cover Function", 86 | "type": "shell", 87 | "command": "npx", 88 | "args": [ 89 | "moduless", 90 | "set", 91 | "${file}:${lineNumber}" 92 | ], 93 | "problemMatcher": [] 94 | }, 95 | { 96 | "label": "Run All Cover Functions", 97 | "type": "shell", 98 | "command": "${workspaceRoot}/node_modules/.bin/electron", 99 | "args": [ 100 | "${workspaceRoot}/node_modules/moduless/build/moduless.js", 101 | "moduless", 102 | "all" 103 | ], 104 | "problemMatcher": [] 105 | } 106 | ] 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /Hat.cover.ts: -------------------------------------------------------------------------------- 1 | 2 | namespace Cover 3 | { 4 | /** */ 5 | export async function coverHat() 6 | { 7 | 8 | } 9 | } 10 | 11 | //@ts-ignore 12 | if (typeof module === "object") Object.assign(module.exports, { Cover }); 13 | -------------------------------------------------------------------------------- /Hat.ts: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * 4 | */ 5 | namespace Hat 6 | { 7 | /** */ 8 | export interface IHat 9 | { 10 | readonly head: Element; 11 | } 12 | 13 | /** */ 14 | type Constructor = 15 | (abstract new (...args: any) => T) | 16 | (new (...args: any) => T); 17 | 18 | /** 19 | * Marks an object as a Hat. (Or formally–an "Anonymous Controller Class"). 20 | */ 21 | export function wear(hat: IHat) 22 | { 23 | const names = getConstructorClassNames(hat); 24 | const hatsArray = hats.get(hat.head) || []; 25 | hatsArray.push(hat); 26 | hats.set(hat.head, hatsArray); 27 | (hat.head as any)._hat = hat; 28 | hat.head.classList.add(...names); 29 | } 30 | 31 | /** 32 | * Enables a hat to have the ability to respond to signaling functions. 33 | * Marks the hat argument as a hat, if it has not been done so already. 34 | */ 35 | export function watch void>(hat: IHat, signal: F, handler: H) 36 | { 37 | if ((hats.get(hat.head) || []).length === 0) 38 | Hat.wear(hat); 39 | 40 | const name = getSignalClassName(signal); 41 | const signalsArray = signals.get(hat.head) || []; 42 | signalsArray.push([signal, handler.bind(hat)]); 43 | signals.set(hat.head, signalsArray); 44 | hat.head.classList.add(name); 45 | } 46 | 47 | /** 48 | * Sends a call signal to all Hats in the document that have subscribed 49 | * to invokations of the specified signal function. 50 | */ 51 | export function signal void>(ref: F, ...args: A) 52 | { 53 | const cls = getSignalClassName(ref); 54 | const elements = document.body.getElementsByClassName(cls); 55 | 56 | for (let i = -1; ++i < elements.length;) 57 | { 58 | const e = elements.item(i); 59 | if (!e) 60 | continue; 61 | 62 | const signalsArray = signals.get(e) || []; 63 | for (const [signalFunction, boundFunction] of signalsArray) 64 | if (signalFunction === ref) 65 | boundFunction(...args); 66 | } 67 | } 68 | 69 | /** */ 70 | function getSignalClassName(fn: (...args: any) => void) 71 | { 72 | if (!fn.name) 73 | throw new Error("Cannot use an unnamed function as signaler"); 74 | 75 | return signalPrefix + fn.name; 76 | } 77 | 78 | const signalPrefix = "signal:"; 79 | 80 | /** 81 | * @returns An array that contains all that hats that have been assigned to 82 | * the specified Node. 83 | */ 84 | export function of(e: Node | null | undefined): T[]; 85 | /** 86 | * @returns The Hat of the specified Node with the specified Hat type, 87 | * or null in the case when the Node is not wearing a Hat of the specified type. 88 | */ 89 | export function of(e: Node | null | undefined, type: Constructor): T | null; 90 | export function of(e: Node | null | undefined, type?: Constructor) 91 | { 92 | if (!e) 93 | return null; 94 | 95 | if (!type) 96 | return (hats.get(e as Element) || []).slice(); 97 | 98 | let current: Node | null = e; 99 | 100 | for (;;) 101 | { 102 | const array = hats.get(current as Element); 103 | 104 | if (array) 105 | for (const obj of array) 106 | if (obj instanceof type) 107 | return obj; 108 | 109 | if (!(current.parentElement instanceof Element)) 110 | break; 111 | 112 | current = current.parentElement; 113 | } 114 | 115 | return null; 116 | } 117 | 118 | /** 119 | * Scans upward through the DOM, starting at the specified object, 120 | * and performs a look-down at each layer in order to find a Hat of 121 | * the specified type, which is nearest in the DOM to the specified 122 | * Node or Hat. 123 | * 124 | * @returns A reference to the Hat that is nearest to the specified 125 | * object. 126 | */ 127 | export function nearest( 128 | via: Node | IHat, 129 | type: Constructor) 130 | { 131 | let current: Node | null = via instanceof Node ? via : via.head; 132 | 133 | while (current instanceof Node) 134 | { 135 | if (current instanceof Element) 136 | { 137 | const maybe = Hat.down(current, type); 138 | if (maybe) 139 | return maybe; 140 | } 141 | current = current.parentElement; 142 | } 143 | 144 | return null; 145 | } 146 | 147 | /** 148 | * Scans upward through the DOM, starting at the specified Node, 149 | * looking for the first element wearing a Hat of the specified type. 150 | * 151 | * @returns A reference to the Hat that exists above the specified Node 152 | * in the DOM, or null if no such Hat was found. 153 | */ 154 | export function up( 155 | via: Node | IHat, 156 | type: Constructor) 157 | { 158 | let current: Node | null = via instanceof Node ? via : via.head; 159 | 160 | while (current instanceof Node) 161 | { 162 | if (current instanceof Element) 163 | { 164 | const hat = Hat.of(current, type); 165 | if (hat) 166 | return hat; 167 | } 168 | current = current.parentElement; 169 | } 170 | 171 | return null; 172 | } 173 | 174 | /** 175 | * Finds the first descendent element that has an attached Hat of the 176 | * specified type, that exists underneath the specified Node or Hat. 177 | * 178 | * @returns The Hat associated with the descendent element, or 179 | * null if no such Hat is found. 180 | */ 181 | export function down(via: Node | IHat, type: Constructor) 182 | { 183 | const hats = within(via, type, true); 184 | return hats.length > 0 ? hats[0] : null; 185 | } 186 | 187 | /** 188 | * Scans upward through the DOM, starting at the specified Node, 189 | * looking for the first element wearing a Hat of the specified type. 190 | * 191 | * @throws An exception if no Hat of the specified type is found. 192 | * @returns The ancestor Hat of the specified type. 193 | */ 194 | export function over( 195 | via: Node | IHat, 196 | type: Constructor) 197 | { 198 | const hat = up(via, type); 199 | if (!hat) 200 | throw new Error("Hat not found."); 201 | 202 | return hat; 203 | } 204 | 205 | /** 206 | * Finds all descendent elements that have an attached Hat of the 207 | * specified type, that exist underneath the specified Node or Hat. 208 | * 209 | * @returns An array of Hats whose type matches the type specified. 210 | */ 211 | export function under(via: Node | IHat, type: Constructor) 212 | { 213 | return within(via, type, false); 214 | } 215 | 216 | /** 217 | * Returns an array of Hats of the specified type, 218 | * which are extracted from the specified array of elements. 219 | */ 220 | export function map(elements: Element[], type: Constructor): T[]; 221 | export function map(elementContainer: Element | IHat, type: Constructor): T[]; 222 | export function map(e: IHat | Element | Element[], type: Constructor): T[] 223 | { 224 | e = (!(e instanceof Element) && !window.Array.isArray(e)) ? e.head : e; 225 | const elements = e instanceof Element ? window.Array.from(e.children) : e; 226 | return elements 227 | .map(e => of(e, type)) 228 | .filter((o): o is T => o instanceof type); 229 | } 230 | 231 | /** 232 | * Returns the element succeeding the specified element in 233 | * the DOM that is connected to a hat of the specified type. 234 | */ 235 | export function next(via: Element | IHat, type: Constructor): T | null 236 | { 237 | via = via instanceof Element ? via : via.head; 238 | 239 | for (;;) 240 | { 241 | via = via.nextElementSibling as Element; 242 | if (!(via instanceof Element)) 243 | return null; 244 | 245 | const hat = of(via, type); 246 | if (hat) 247 | return hat; 248 | } 249 | } 250 | 251 | /** 252 | * Returns the element preceeding the specified element in the DOM 253 | * that is connected to a hat of the specified type. 254 | */ 255 | export function previous(via: Element | IHat, type: Constructor): T | null 256 | { 257 | via = via instanceof Element ? via : via.head; 258 | 259 | for (;;) 260 | { 261 | via = via.previousElementSibling as Element; 262 | if (!(via instanceof Element)) 263 | return null; 264 | 265 | const hat = of(via, type); 266 | if (hat) 267 | return hat; 268 | } 269 | } 270 | 271 | /** */ 272 | function within(via: Node | IHat, type: Constructor, one: boolean) 273 | { 274 | const e = 275 | via instanceof Element ? via : 276 | via instanceof Node ? via.parentElement : 277 | via.head; 278 | 279 | if (!e) 280 | throw "Cannot perform this method using the specified node."; 281 | 282 | const names = ctorNames.get(type); 283 | 284 | // If there is no class name found for the specified hat type, 285 | // this could possibly be an error (meaning that the hat type 286 | // wasn't registered). But it could also be a legitimate case of the 287 | // hat simply not having been registered at the time of this 288 | // function being called. 289 | if (!names || names.length === 0) 290 | return []; 291 | 292 | const descendents = names.length === 1 ? 293 | e.getElementsByClassName(names[0]) : 294 | e.querySelectorAll(names.map(n => "." + n).join()); 295 | 296 | const hats: T[] = []; 297 | const len = one && descendents.length > 0 ? 1 : descendents.length; 298 | 299 | for (let i = -1; ++i < len;) 300 | { 301 | const descendent = descendents[i]; 302 | const hat = Hat.of(descendent, type); 303 | if (hat) 304 | hats.push(hat); 305 | } 306 | 307 | return hats; 308 | } 309 | 310 | /** */ 311 | function childrenOf(e: Element, hatType?: Constructor) 312 | { 313 | let children = globalThis.Array.from(e.children); 314 | 315 | if (hatType) 316 | children = children.filter(e => Hat.of(e, hatType)); 317 | 318 | return children; 319 | } 320 | 321 | /** 322 | * Returns a unique CSS class name that corresponds to the type 323 | * of the object. 324 | */ 325 | function getConstructorClassNames(object: object) 326 | { 327 | const existingNames = ctorNames.get(object.constructor); 328 | if (existingNames) 329 | return existingNames; 330 | 331 | const ctors: any[] = [object.constructor]; 332 | const names: string[] = []; 333 | 334 | for (;;) 335 | { 336 | const ctor = ctors[ctors.length - 1]; 337 | const next = Object.getPrototypeOf(ctor); 338 | if (next === null || next === Object || next === Function) 339 | break; 340 | 341 | ctors.push(next); 342 | } 343 | 344 | for (const ctor of ctors) 345 | { 346 | let name = ctor.name || ""; 347 | 348 | if (name.length < 3) 349 | name = "_hat_" + name + (++inc); 350 | 351 | names.push(name); 352 | } 353 | 354 | for (let i = ctors.length; i-- > 0;) 355 | { 356 | const ctor = ctors[i]; 357 | if (!ctorNames.has(ctor)) 358 | ctorNames.set(ctor, names.slice(i)); 359 | } 360 | 361 | return names; 362 | } 363 | 364 | const ctorNames = new WeakMap(); 365 | const hats = new WeakMap(); 366 | const signals = new WeakMap(); 367 | let inc = 0; 368 | 369 | /** 370 | * 371 | */ 372 | export class Array 373 | { 374 | /** */ 375 | constructor( 376 | private readonly parentElement: Element, 377 | private readonly hatType: Constructor) 378 | { 379 | this.marker = document.createComment(""); 380 | parentElement.append(this.marker); 381 | } 382 | 383 | private readonly marker: Comment; 384 | 385 | /** */ 386 | * [Symbol.iterator]() 387 | { 388 | for (let i = -1; ++i < this.parentElement.children.length;) 389 | { 390 | const child = this.parentElement.children.item(i); 391 | if (child) 392 | { 393 | const hat = Hat.of(child, this.hatType); 394 | if (hat) 395 | yield hat; 396 | } 397 | } 398 | } 399 | 400 | /** */ 401 | map(): THat[]; 402 | map(mapFn: (value: THat, index: number, array: THat[]) => T): T[]; 403 | map(mapFn?: (value: THat, index: number, array: THat[]) => any) 404 | { 405 | const elements = childrenOf(this.parentElement, this.hatType); 406 | const hats = Hat.map(elements, this.hatType); 407 | return mapFn ? hats.map(mapFn) : hats; 408 | } 409 | 410 | /** */ 411 | at(index: number) 412 | { 413 | return this.map().at(index) || null; 414 | } 415 | 416 | /** */ 417 | insert(...hats: THat[]): number; 418 | insert(index: number, ...hats: THat[]): number; 419 | insert(a: number | THat, ...newHats: THat[]) 420 | { 421 | const index = typeof a === "number" ? (a || 0) : -1; 422 | const existingHats = this.map(); 423 | 424 | if (typeof a === "object") 425 | newHats.unshift(a); 426 | 427 | if (newHats.length === 0) 428 | return; 429 | 430 | if (existingHats.length === 0) 431 | { 432 | this.parentElement.prepend(...newHats.map(c => c.head)); 433 | } 434 | else 435 | { 436 | const target = index >= existingHats.length ? 437 | (existingHats.at(index) as IHat).head : 438 | this.marker; 439 | 440 | for (const hat of newHats) 441 | this.parentElement.insertBefore(hat.head, target); 442 | } 443 | 444 | return index < 0 ? existingHats.length + newHats.length : index; 445 | } 446 | 447 | /** */ 448 | move(fromIndex: number, toIndex: number) 449 | { 450 | const children = childrenOf(this.parentElement, this.hatType); 451 | const target = children.at(toIndex); 452 | const source = children.at(fromIndex); 453 | 454 | if (source && target) 455 | target.insertAdjacentElement("beforebegin", source); 456 | } 457 | 458 | /** */ 459 | indexOf(hat: THat) 460 | { 461 | const children = childrenOf(this.parentElement, this.hatType); 462 | for (let i = -1; ++i < children.length;) 463 | if (children[i] === hat.head) 464 | return i; 465 | 466 | return -1; 467 | } 468 | 469 | /** */ 470 | get length() 471 | { 472 | return childrenOf(this.parentElement, this.hatType).length; 473 | } 474 | 475 | /** */ 476 | observe(callback: (mut: MutationRecord) => void) 477 | { 478 | if (this.observers.length === 0) 479 | { 480 | const mo = new MutationObserver(mutations => 481 | { 482 | for (const mut of mutations) 483 | for (const fn of this.observers) 484 | fn(mut); 485 | }); 486 | 487 | mo.observe(this.parentElement, { childList: true }); 488 | } 489 | 490 | this.observers.push(callback); 491 | } 492 | 493 | private readonly observers: ((mut: MutationRecord) => void)[] = []; 494 | 495 | /** */ 496 | private toJSON() 497 | { 498 | return this.map(); 499 | } 500 | } 501 | } 502 | 503 | //@ts-ignore CommonJS compatibility 504 | if (typeof module === "object") Object.assign(module.exports, { Hat }); 505 | 506 | // ES module compatibility 507 | declare module "hatjs" 508 | { 509 | const __export: { Hat: typeof Hat }; 510 | export = __export; 511 | } 512 | 513 | // The comment and + prefix is removed during npm run bundle 514 | //+ export { Hat } 515 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Paul Gordon 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@squaresapp/hatjs", 3 | "author": "Paul Gordon", 4 | "description": "Utility Functions For Anonymous Controller Classes", 5 | "version": "1.0.3", 6 | "license": "MIT", 7 | "repository": { 8 | "type": "git", 9 | "url": "https://github.com/squaresapp/hatjs" 10 | }, 11 | "bugs": { 12 | "url": "https://github.com/squaresapp/hatjs/issues" 13 | }, 14 | "main": "hat.js", 15 | "browser": "hat.js", 16 | "module": "hat.esm.js", 17 | "types": "hat.d.ts", 18 | "publishConfig": { 19 | "access": "public" 20 | }, 21 | "files": [ 22 | "LICENSE.txt", 23 | "readme.md", 24 | "hat.js", 25 | "hat.min.js", 26 | "hat.d.ts", 27 | "hat.d.ts.map", 28 | "hat.esm.js" 29 | ], 30 | "scripts": { 31 | "test": "exit 0", 32 | "esm": "sed 's/\"use strict\";/export/' ./+build/hat.js > ./+build/hat.esm.js", 33 | "bundle": "tsc -p tsconfig.release.json && terser ./+build/hat.js > ./+build/hat.min.js && npm run esm", 34 | "release": "npm run bundle && cd ./+build && cp ../package.json ./package.json && np && cp ./package.json ../package.json" 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | 2 | # HatJS - Utility Functions For Anonymous Controller Classes 3 | 4 | HatJS is a library designed to support the Anonymous Controller Class (ACC) pattern. Technically, you don't need any library to implement this pattern into your code, but using HatJS adds some helpful utility functions that allow you to easily discover and manipulate the anonymous controllers that are associated with a given HTML element. 5 | 6 | ## Installation 7 | 8 | From jsDelivr: 9 | ```html 10 | 11 | ``` 12 | Typings 13 | ``` 14 | npm install @squaresapp/hatjs 15 | ``` 16 | 17 | ## What are Anonymous Controller Classes? 18 | 19 | Anonymous Controller Classes are a code pattern for organizing vanilla JS apps into a coherent structure. They're classes that wrap the root HTML element of a component, and provide a place for the backing logic that supports its operation. 20 | 21 | Below is an example of an Anonymous Controller Class in action: 22 | 23 | ```typescript 24 | class SomeComponent { 25 | readonly head; 26 | 27 | constructor() { 28 | this.head = document.createElement("div"); 29 | this.head.addEventListener("click', () => this.click()); 30 | // Probably do some other stuff to this.head 31 | } 32 | 33 | private handleClick() { 34 | alert("Clicked!") 35 | } 36 | } 37 | ``` 38 | 39 | ACCs are classes that create and wrap a root element, which possibly may container other nested elements, with event listeners connected, styling assigned, etc. They have methods which are typically event handlers or other helper methods. You then instanitate the component, and add the component's .head element to the DOM: 40 | 41 | ```typescript 42 | const component = new SomeComponent(); 43 | document.body.append(component.head); 44 | ``` 45 | 46 | The class is considered "anonymous" because you can discard your instance of the component as soon as its attached to the DOM. The instance of the class will be garbage collected as soon as the element is removed from the DOM and garbage collected. For example: 47 | 48 | ```typescript 49 | class SomeComponent { 50 | readonly head; 51 | 52 | constructor() { 53 | this.head = document.createElement("div"); 54 | this.head.addEventListener("click', () => this.remove()); 55 | // Probably do some other stuff to this.head 56 | } 57 | 58 | private remove() { 59 | // Remove the component's .head element from the DOM, 60 | // which will by extension garbage collect this instance 61 | // of SomeComponent. 62 | this.head.remove(); 63 | } 64 | } 65 | ``` 66 | 67 | ACCs impose no restrictions on you. They can inherit from anything (or nothing). They're just an idea––you can mold them to behave however you like. 68 | 69 | There are many scenarios when you might want to get the ACC associated with a particular element. For example, imagine iterating through the ancestor elements of the this.head element, and getting the ACCs associated with it in order to invoke some public method. **This is where HatJS comes in**. 70 | 71 | ## HatJS Utility Functions 72 | 73 | The HatJS library is a stateless library of utility functions that allow you to inspect the ACCs associated with an element, and send various signals between them. 74 | 75 | `Hat.wear(object)` - Marks an object as a Hat. (Or formally–an "Anonymous Controller Class"). 76 | 77 | `Hat.of(element, HatType)` - Gets a hat, optionally of a specified type, that is associated with the specified element. 78 | 79 | `Hat.nearest(element, HatType)` - Returns a reference to the Hat that is nearest in the DOM to the specified element. 80 | 81 | `Hat.up(element, HatType)` - Scans upward through through the DOM, starting at the specified element, until it finds a Hat of the specified type. (Can return null) 82 | 83 | `Hat.down()` - Finds the first descendent element that has an attached Hat of the specified type, that exists underneath the specified Node (or Hat). 84 | 85 | `Hat.over()` - Scans upward through the DOM, starting at the specified Node, looking for the first element wearing a Hat of the specified type. (Throws an exception) 86 | 87 | `Hat.under()` - Finds all descendent elements that have an attached Hat of the specified type, that exist underneath the specified Node or Hat. 88 | 89 | `Hat.watch(object, signal, handler)` - Enables a hat to have the ability to respond to signaling functions. 90 | 91 | `Hat.signal(SignalingFunction, ...signalingFunctionArguments)` - Invokes the watch handlers on all other hats in the DOM that have subscribed to the specified signaling function, and passes the specified arguments to each call. 92 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "outFile": "+build/hat.debug.js", 4 | "composite": true, 5 | "module": "system", 6 | "moduleResolution": "node", 7 | "target": "esnext", 8 | "inlineSourceMap": true, 9 | "inlineSources": true, 10 | "strict": true, 11 | "baseUrl": "./", 12 | "rootDir": ".", 13 | "declaration": true, 14 | "declarationMap": true, 15 | "stripInternal": true, 16 | "incremental": true, 17 | "preserveConstEnums": true, 18 | "lib": [ 19 | "dom", 20 | "esnext", 21 | "esnext.array", 22 | "esnext.asynciterable" 23 | ] 24 | }, 25 | "include": [ 26 | "Hat.ts", 27 | "Hat.cover.ts", 28 | ] 29 | } 30 | -------------------------------------------------------------------------------- /tsconfig.release.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outFile": "+build/hat.js" 5 | }, 6 | "include": [ 7 | "Hat.ts" 8 | ] 9 | } 10 | --------------------------------------------------------------------------------