{"_id":"@camilaprav/dominant","name":"@camilaprav/dominant","dist-tags":{"latest":"0.1.20"},"versions":{"0.1.20":{"name":"@camilaprav/dominant","version":"0.1.20","description":"Dysfunctional JavaScript UI library","author":{"name":"Gui Prá"},"license":"ISC","keywords":["dominant"],"repository":{"type":"git","url":"git+https://github.com/camilaprav/dominant.git"},"main":"index.js","scripts":{"test":"mocha index.spec.js","build":"cd demos; ./build.sh"},"babel":{"plugins":["@babel/plugin-transform-modules-commonjs","@babel/plugin-proposal-class-properties","./babelatrix",["@babel/plugin-transform-react-jsx",{"pragma":"d.el","pragmaFrag":"d.JsxFragment","throwIfNamespace":false}]]},"devDependencies":{"@babel/core":"^7.12.10","@babel/plugin-proposal-class-properties":"^7.12.1","@babel/plugin-transform-modules-commonjs":"^7.12.1","@babel/plugin-transform-react-jsx":"^7.12.12","@babel/types":"^7.12.12","@types/chai":"^4.2.11","@types/jsdom":"^16.2.3","@types/mocha":"^8.0.0","@types/sinon":"^9.0.4","babelify":"^10.0.0","browserify":"^17.0.0","chai":"^4.2.0","cypress":"^12.17.3","d3-scale-chromatic":"^2.0.0","jsdom":"^16.3.0","mocha":"^8.0.1","perf-monitor":"^0.4.1","sinon":"^9.0.2"},"publishConfig":{"access":"public"},"_id":"@camilaprav/dominant@0.1.20","gitHead":"699eee0f7c10b87e93032f0185628206292ebfad","bugs":{"url":"https://github.com/camilaprav/dominant/issues"},"homepage":"https://github.com/camilaprav/dominant#readme","_nodeVersion":"23.11.0","_npmVersion":"10.9.2","dist":{"integrity":"sha512-iKIh5eCTOBg0a9ymAp/UoXsG6yJlVAd8r9/A51bfjDZWV7aRuJwjmiJuA8aFdIM9FYTCfT+2zMf6vJV1xdA6qg==","shasum":"fe360f70a165b9972e5787295006d7e064cdc33e","tarball":"https://registry.npmjs.org/@camilaprav/dominant/-/dominant-0.1.20.tgz","fileCount":15,"unpackedSize":127042,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEQCIFq0RGXovZ8cT48sgLW1UUHnH9O7ZCTddoae9JpCr5ptAiBiJKG3HAy5KzFEx3zAuK6OGYZjhTOG1lNaR3PdyLulKw=="}]},"_npmUser":{"name":"camilaprav","email":"camila@guiprav.com"},"directories":{},"maintainers":[{"name":"camilaprav","email":"camila@guiprav.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/dominant_0.1.20_1755293560238_0.9688527054688694"},"_hasShrinkwrap":false}},"time":{"created":"2025-08-15T21:32:40.151Z","0.1.20":"2025-08-15T21:32:40.433Z","modified":"2025-08-15T21:32:40.695Z"},"maintainers":[{"name":"camilaprav","email":"camila@guiprav.com"}],"description":"Dysfunctional JavaScript UI library","homepage":"https://github.com/camilaprav/dominant#readme","keywords":["dominant"],"repository":{"type":"git","url":"git+https://github.com/camilaprav/dominant.git"},"author":{"name":"Gui Prá"},"bugs":{"url":"https://github.com/camilaprav/dominant/issues"},"license":"ISC","readme":"# <img src=\"logo.svg\" alt=\"Dominant logo\" height=\"40\" align=\"top\"> Dominant – Dysfunctional JavaScript UIs\n\n[React](http://reactjs.org) has been truly revolutionary back in the day, and it's taught us many important lessons, but I think it's about time we move on, and I'm not excited about any of the existing alternatives as they're all similarly complex.\n\nI need a UI library that allows me to create components bound to mutable JavaScript state, that's it.\n\nI can call an update function whenever state changes (à la [Mithril](https://mithril.js.org)), so there's no need to track changes.\n\n  * This means no special APIs for changing state; just mutate your variables and objects.\n  * Also no observables or hacky object property/array method monkey-patching (like [Aurelia](https://aurelia.io) and [VueJS](https://vuejs.org) do).\n  * A global **update** function reevaluates all bindings and updates the DOM strictly as needed.\n\nIt should let me leverage DOM APIs, not abstract them away.\n\n  * This means no virtual DOM.\n  * It also means there's no **mount** function; components are just functions or classes with render functions that return DOM nodes you can compose using a familiar [JSX](https://reactjs.org/docs/introducing-jsx.html) and/or [Hyperscript](https://github.com/hyperhype/hyperscript)-like API and append wherever.\n  * A DOM [mutation observer](https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver) keeps track of which DOM nodes with bindings are attached to the document and calls lifecycle listeners (**attach**/**detach**, if any).\n  * This plays well with other DOM-based UI libraries, such as vanilla JS components and jQuery UI.\n\nDon't miss the [demos](#demos) and [API documentation](#api) sections.\n\n## Setup\n\nThe easiest way to bootstrap a Dominant project is to use [Create Dominant App](https://www.npmjs.com/package/create-dominant-app).\nIf you have npm installed, the following command is all that's necessary:\n\n```sh\n# Replace your-app-name with the desired project name.\n$ npm init dominant-app your-app-name\n```\n\nnpm will automatically install [create-dominant-app](https://www.npmjs.com/package/create-dominant-app) and run it for you.\n`create-dominant-app` will create your new app directory, initialize `package.json` in it, and install all the necessary dependencies.\n\nOnce the script finishes, you'll be able to change into the newly created directory and start the development server:\n\n```sh\n$ cd your-app-name\n$ npm start\n```\n\nThen open http://localhost:9966/ to see your app.<br>\nWhen you're ready to deploy to production, create a minified bundle with `npm run build`.\n\n## Demos\n\n### To-Do App\n\nBased on [TodoMVC](https://todomvc.com/):\n\n[![To-Do App demo screenshot](demos/TodoApp/screenshot.png)](https://dominant-demos.netlify.app/todoapp)\n\n### 1k Components\n\nHeavy real-time animation performance demo:\n\n[![1k Components demo screenshot](demos/1kComponents/screenshot.png)](https://dominant-demos.netlify.app/1kcomponents)\n\n## API\n\n### d.el(tagName | fn | Component, { props }?, ...children)\n\nAll JSX tags are automatically converted into calls to this function. It creates a DOM element of the specified tag name (`tagName`), function (`fn`), or class (`Component`).\n\nWhen a tag name is supplied, key/values in the (optional) **props** object can be used to set an element's attributes, properties, bindings, and event listeners.\n\nAlso any **children** (optional) are appended to the created element.\n\nWhen a component function or class is supplied, any  **children** supplied are stored in `props.children` before **props** is forwarded to the component function or class constructor.\n\nWhen a component function is supplied, the return value of `fn(props)` is returned.\n\nWhen a component class is supplied, the return value of `new Component(props).render()` is returned.\n\n```jsx\n// Example variables, see their usage below:\nlet shouldHideDiv = false;\n\nlet someImage = {\n  alt: 'GNU logo',\n  src: 'https://www.gnu.org/graphics/heckert_gnu.transp.small.png',\n};\n\nlet isThirsty = true;\nlet someBgColorVariable = 'yellow';\nlet currentInputValue = 'hello';\n\ndocument.body.append(\n  // Static prop examples:\n  <div class=\"foo bar\">foobar</div>,\n  <input type=\"text\" value=\"foo\" />,\n  <img alt=\"bar\" src=\"baz.png\" />,\n  <button onClick={() => alert('quux')}>Click me</button>,\n\n  // Dynamic prop examples (one-way bindings):\n  <div hidden={() => shouldHideDiv} />,\n  <img alt={() => someImage.description} src={() => someImage.url} />,\n\n  // Static style prop example:\n  <div style={{\n    width: '40px',\n    height: '40px',\n    border: '1px solid red',\n    backgroundColor: 'blue',\n  }} />,\n\n  // Dynamic class and style props example (one-way bindings):\n  <div\n    class={[\n      'foo', // foo class will be statically added to the element on first render.\n      () => isThirsty && 'bar', // bar class will be dynamically added/removed according to isThirsty.\n    ]}\n\n    style={{\n      // width, height, and border are all gonna be statically set on the element on first render.\n      width: '40px',\n      height: '40px',\n      border: '1px solid red',\n\n      // backgroundColor will be dynamically bound to someBgColorVariable.\n      backgroundColor: () => someBgColorVariable,\n    }}\n  />,\n\n  // Input value prop example (two-way binding):\n  <input\n    value={d.binding({\n      get: () => currentInputValue,\n      set: x => currentInputValue = x,\n    })}\n  />,\n);\n\n// Component function:\nconst HelloFn = ({ whom }) => d.text(() => `Hello, ${d.resolve(whom)}!`);\ndocument.body.append(<HelloFn whom=\"functions\" />);\n\n// Component class (with d.resolving property getter `this.whom`):\nclass HelloClass extends d.Component {\n  constructor(props) {\n    super();\n    this.props = props;\n  }\n\n  // This getter calls `d.resolve(this.props.whom)` internally so you don't have\n  // to do that every time you want `this.props.whom`'s resolved value. See\n  // `d.resolve`'s documentation below.\n  get whom() {\n    return d.resolve(this.props.whom);\n  }\n\n  render = () => d.text(() => `Hello, ${this.whom}!`);\n}\n\ndocument.body.append(<HelloClass whom=\"classes\" />);\n```\n\n**Note:** Dominant has no way of knowing when your application's state changes.\nIt's up to you to call **d.update()** after any known or potential state changes.\n\n### d.resolve(x)\n\nThis helper function will call **x** if it's a function, or just return **x** itself otherwise. That is:\n\n```jsx\nd.resolve(() => 123); // returns 123.\nd.resolve(123); // also returns 123.\n```\n\nThis is useful when you're writing a component which may receive a regular value as prop or a getter function that works as a live reference to some expression in the getter function's scope. E.g.:\n\n\n```jsx\nlet name = 'John Doe';\n\n// Since we're passing `name` here directly, we're actually passing `name`'s\n// current value as a constant.\ndocument.body.append(<HelloFn whom={name} />);\n\n// I.e., this has no effect:\nname = 'Jane Doe';\nd.update();\n\n// If we pass a getter function, on the other hand, HelloFn can call it anytime\n// to get the most up-to-date value:\ndocument.body.append(<HelloFn whom={() => name} />);\n\n// So this causes the UI to update accordingly:\nname = 'Foo Bar';\nd.update();\n```\n\n### d.update()\n\nReevaluates all DOM data bindings set with **d.el**, executing all the supplied functions and comparing return values with the ones from previous invocations.\n\nOnly bindings whose values have changed since the last invocation are applied to the DOM.\n\n```jsx\nlet color = 'blue';\nlet whom = 'world';\n\nsetTimeout(() => {\n  color = 'red';\n  whom = 'human';\n\n  d.update();\n}, 1000);\n\ndocument.body.append(\n  <div style={{ color: () => color }}>\n    Hello, {d.text(() => whom)}!\n  </div>\n);\n```\n\n### d.text(fn)\n\nReturns a DOM text node with contents bound to the supplied **fn**.\n\nWhenever **d.update** gets called, text bindings are reevaluated, meaning the supplied functions are reexecuted and their return values are compared to the return values from previous invocations.\n\nOnly text bindings whose values have changed since the last invocation are updated in the DOM.\n\n```jsx\nlet whom = 'world';\n\nsetTimeout(() => {\n  whom = 'human';\n  d.update();\n}, 1000);\n\ndocument.body.append(d.text(() => `Hello, ${whom}!`));\n```\n\n### d.if(predFn, thenNode, elseNode)\n\nReturns a conditional anchor comment node (`<!-- anchorComment: if -->`) that represents a conditional node attachment in the document.\n\nWhen updated, the binding calls `predFn` and adds `thenNode` as its next sibling if the result is truthy, `elseNode` otherwise.\n\nNote: Nodes, including anchor comment nodes, are automatically updated when attached to the document (`d.mutationObserver` does this).\n\n```jsx\nlet isNewVisitor = true;\n\ndocument.body.append(d.if(\n  () => isNewVisitor,\n  <div>Nice to meet you!</div>,\n  <div>Welcome back!</div>,\n));\n\nsetTimeout(() => {\n  isNewVisitor = false;\n  d.update();\n}, 1000);\n```\n\n### d.map(arrayFn, fn)\n\nThe `array.map(fn)` analog to `d.if`.\n\nWhen updated, the binding calls `arrayFn`, removes nodes associated to removed array values, reorders nodes to match the order of associated values in the new array, maps new values to new nodes using `fn`, and adds them to the DOM.\n\n```jsx\nlet fruits = [\n  { name: 'Apple', color: 'Red' },\n  { name: 'Grape', color: 'Purple' },\n  { name: 'Lime', color: 'Green' },\n];\n\nlet wikipediaPagePrefix = 'https://en.wikipedia.org/wiki';\n\ndocument.body.append(d.map(\n  () => fruits, fruit => (\n    <li>\n      The <a href={() => `${wikipediaPagePrefix}/${fruit.name}`}>\n        {d.text(() => fruit.name)}\n      </a> is <a href={() => `${wikipediaPagePrefix}/${fruit.color}`}>\n        {d.text(() => fruit.color)}\n      </a>.\n    </li>\n  ),\n));\n```\n\n## License\n\n### ISC (Internet Systems Consortium)\n\nDominant is free software: you can redistribute it and/or modify it under the terms of the [ISC License](COPYING).\n\n## Exclusion of warranty\n\nTHE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.\n","readmeFilename":"README.md","_rev":"1-51a61d977abd8905558fe00ec5d3c8b0"}