{"id":25638340,"url":"https://github.com/dolanske/cascade","last_synced_at":"2026-02-11T16:02:12.042Z","repository":{"id":214245476,"uuid":"736047887","full_name":"dolanske/cascade","owner":"dolanske","description":"Write reactive UI components using render functions. ","archived":false,"fork":false,"pushed_at":"2025-01-08T22:19:07.000Z","size":410,"stargazers_count":1,"open_issues_count":1,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-09-05T23:34:56.381Z","etag":null,"topics":["framework","jquery","react","vue"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"gpl-3.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/dolanske.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2023-12-26T20:50:14.000Z","updated_at":"2025-01-08T22:19:08.000Z","dependencies_parsed_at":"2024-01-13T19:59:27.046Z","dependency_job_id":"6d0b9c94-46e1-4b04-b78b-e0278b9d0c77","html_url":"https://github.com/dolanske/cascade","commit_stats":null,"previous_names":["dolanske/cascade"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/dolanske/cascade","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dolanske%2Fcascade","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dolanske%2Fcascade/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dolanske%2Fcascade/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dolanske%2Fcascade/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/dolanske","download_url":"https://codeload.github.com/dolanske/cascade/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dolanske%2Fcascade/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":29337001,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-02-11T16:00:30.228Z","status":"ssl_error","status_checked_at":"2026-02-11T16:00:25.398Z","response_time":97,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["framework","jquery","react","vue"],"created_at":"2025-02-23T02:24:53.997Z","updated_at":"2026-02-11T16:02:12.025Z","avatar_url":"https://github.com/dolanske.png","language":"TypeScript","readme":"# cascade\n\nI swear this is the last DOM library I make (for now)\n\nCreate simple, reusable and reactive UI components using render functions and add more complex functionality through method chaining. These methods can be mounted anywhere in the DOM, static applications, with added reactivity only where needed.\n\n```bash\nnpm i @dolanske/cascade\n```\n\n---\n\n## Concept\n\nCreate UI components by calling a component function. All supported HTML elements have their own factory function.\n\n- Provide children when calling the component\n- Chain function to extend the functionality\n\n```ts\nconst button = button('Click me').on('click', () =\u003e console.log('I got clicked!!'))\n```\n\nMany function allow you to pass a ref or a getter function. These allow you to reactively update the UI. To familiarize yourself with these concepts, read the [Vue documentation on this topic](https://vuejs.org/api/reactivity-core.html).\n\n## Components\n\nThere are two ways of creating components. The instanceless and reactive components.\n\n- **Instanceless** components are basic UI. Think of it as scaffolding. For instance `\u003cdiv class=\"wrapper\"\u003e\u003c/div\u003e` does not hold any state, it's there to provided a container with some styling, but that's where its journey ends\n- **Reusable** the meat of your application. These components for instance provide interactivity and/or fetch data. Based on their state, we want to update the UI.\n\nIt is heavily discouraged to reuse instanceless components multiple times. Every single component has an instance.\n\n```ts\nconst Container = div().className('container')\n\n// In component A\nContainer.nest(h1('Hello'))\n// In component B\nContainer.nest(h2('World'))\n// Both components will have \u003ch2\u003eWorld\u003c/h2\u003e as the `Container` component has just one instance.\n```\n\nTo create a reusable component, you need to either use the `reusable` function. This will create a unique component instance each time it is used.\n\n```ts\nconst Container = reusable('div', (ctx, props) =\u003e {\n  // Create a component which will wrap the provided child nodes in 3 divs\n  ctx.nest(\n    h1(props.title),\n    div(ctx.children).class('wrapper')\n  )\n})\n\n// Later used in a component\nconst app = App(\n  Container(\n    span('Subtitle')\n  ).prop('title', 'Hello world')\n)\n\napp.mount('#app')\n```\n\n---\n\n\u003e [!NOTE]\n\u003e API docs are work in progress\n\n## API\n\nCreating a component returns a component instance. This instance contains a few useful properties and a lot of methods.\n\n### Instance\n\n```ts\nconst ctx = div(span('hi'))\n\n// Unique ID of the component\nctx.identifier\n// Reference to the mounted DOM node\nctx.el\n// Child component instances\nctx.componentChildren\n// Stores child components which were passed during component initialization.\nctx.children\n// Reference to the parent component, if there's one\nctx.parent\n```\n\n### Content\n\nType definition. Most functions allow the usage of the following type. Using ref or getter function makes the UI reactive. All the examples below assume you're writing code inside the `setup()` function.\n\n```\ntype MaybeRefOrGetter\u003cT\u003e = T | Ref\u003cT\u003e | () =\u003e T\n```\n\n#### `.text()`\n\nSets the `textContent` of the Component's DOM node\n\n```ts\nctx.text(value: MaybeRefOrGetter\u003cPrimitive\u003e)\n```\nExample\n```ts\nctx.text('Hello world')\n// Will update text each time `name` ref changes\nctx.text(() =\u003e `Hello ${name.value}`)\n```\n\n#### `.html()`\n\nSets the `innerHTML` of the Component's DOM node\n\n```ts\nctx.html(value: MaybeRefOrGetter\u003cPrimitive\u003e)\n```\nExample\n```ts\nctx.html('Hello \u003cb\u003eworld\u003c/b\u003e')\nctx.html(() =\u003e SVGIcon.value)\n```\n\n#### `.nest()`\n\nAllows nesting of components and HTML elements.\n\n```ts\n// NOTE: it is planned to allow refs and getter functions, but that does not work yet.\ntype ComponentChildren = string | number | Component | Element | Fragment\nctx.nest(...value: ComponentChildren | ComponentChildren[])\n```\nExample\n```ts\nctx.nest(\n  h1('Hi'),\n  'Hmm',\n  document.createElement('input'),\n  ctx.children\n)\n```\n\n#### `.for()`\n\nIterate over the provided object / array / number and execute the provided\ncallback for each item. Components returned from the callback are then\nrendered.\n\nIt is recommended not to use other chained methods when using `for`,\nbecause the base element is replaced with the return value of the callback\nfunction. All logic should therefore be handled there.\n\n```ts\nexport type Source = any[] | number | object\n\nexport type CallbackType\u003cT\u003e =\n  T extends any[]\n    ? (value: T[number], index: number) =\u003e ComponentChildrenItems\n    : T extends object\n      ? (value: keyof T, key: string, index: number) =\u003e ComponentChildrenItems\n      : (index: number) =\u003e ComponentChildrenItems\n\nctx.for(\n  source: MaybeRefOrGetter\u003cSource\u003e,\n  callback: CallbackType\u003cUnwrapRef\u003cSource\u003e\u003e,\n)\n```\n\nExample\n```ts\nctx.for(['One', 'Two', 'Three'], (item, index) =\u003e {\n  return li(`${index + 1} ${item}`)\n})\n```\n\n---\n\n### Events\n\nRegister event listeners.\n\n#### `.on()`\n\nBind an event listener to the underlying HTML node. The event listener is removed when the component is destryoyed/unmounted. Event modifiers are planned and will be added later.\n\n```ts\nctx.on(type: keyof HTMLElementEventMap, listener: ListenerFn, options?: Options)\n```\n\n```ts\nctx.on('click', (e) =\u003e {\n  e.stopPropagation()\n  clicked.value = true\n})\n```\n\nBy using `.on` you can also listen for sub-component emits. The `.emit` method is just a shorthand for creating and dispatching a custom event from the component HTML node.\n\n```ts\n// Child component\nctx.emit(\"someData\", {\n  name: 'test',\n})\n\n// Somewhere up in the component chain\nctx.on(\"someData\", (event, data) =\u003e {\n  // data = { name : 'test' }\n})\n\n```\n\n#### Event shorthands\n\nA few event definition shorthands are available to make development faster\n\n```ts\nctx.click(listener: ListenerFn, options?: Options)\nctx.submit(listener: ListenerFn, options?: Options)\nctx.focus(listener: ListenerFn, options?: Options)\nctx.blur(listener: ListenerFn, options?: Options)\nctx.change(listener: ListenerFn, options?: Options)\nctx.input(listener: ListenerFn, options?: Options)\n```\n\n#### Keyboard events\n\nDetect keyboard presses on the component.\n\n```ts\nctx.keydown(listener: ListenerFn, options?: Options)\nctx.keyup(listener: ListenerFn, options?: Options)\nctx.keypress(listener: ListenerFn, options?: Options)\n```\n\nYou can also listen for a specific key combination using the `exact` suffix.\nThe options object also receives a new property called `detect` which can be set to `every` or `some`. The default is `every` and it controls wether the function detects keys in the exact order, or any of the provided keys.\n\n```ts\nctx.keydownExact(requiredKeyOrKeys: string | string[], listener: ListenerFn, options?: Options)\nctx.keyupExact(requiredKeyOrKeys: string | string[], listener: ListenerFn, options?: Options)\nctx.keypressExact(requiredKeyOrKeys: string | string[], listener: ListenerFn, options?: Options)\n```\nExample\n```ts\nctx.keypressExact(['Shift', 'A'], () =\u003e {\n  // Fired when SHIFT and A are pressed in succession\n})\n\nctx.keypressExact(['A', 'B', 'C'], () =\u003e {\n  // Fired whenever A, B or C are pressed\n}, { detect: 'some' })\n```\n\n#### `.model()`\n\nTwo way binding to control and element's value with ref. You can use `model()` on `input`, `select`, `textarea` and `details`. \n\nYou can also add a model to a Component, but please note it will only pass it as an additional, untyped prop called `modelValue`. So this usage is heavily discouraged. You can simply pass the ref as a prop and it will already be two way bound, as they are passed `as is` meaning the ref you give to a component is the exact same one from the parent. There's no inbetween.\n\n```ts\n// A function which transforms the value of the element before it's assigned to the provided ref\ntype ModelTransform\u003cReturns = string\u003e = (value: string) =\u003e Returns\n\ninterface ModelOptions {\n  lazy?: boolean\n  transforms?: ModelTransform[]\n  eventOptions?: EventListenerOptions\n}\n\nctx.model(value: Ref\u003cPrimitive | Primitive[]\u003e, options: ModelOptions)\n```\n\nThe implementation follows the [basic usage](https://vuejs.org/guide/essentials/forms#basic-usage) of Vue's `v-model` implementation.\n\nAdditionally, you can control wether the `\u003cdetails\u003e` element is open using `model` by providing it a `Ref\u003cBoolean\u003e`.\n\n---\n\n### Attributes\n\nReactively bind attributes to the underlying HTML element.\n\n#### `.class()`\n\nBind static or reactive class / class object.\n\n```ts\ntype ClassObject = Record\u003cstring, MaybeRefOrGetter\u003cboolean\u003e\u003e\ntype ClassnameOrCLassObject = string | ClassObject\n\nctx.class(classNames?: ClassnameOrCLassObject, value?: MaybeRefOrGetter\u003cboolean\u003e)\n```\nExample\n```ts\n// Single ref class\nconst largeText = ref(false)\nctx.class('text-xl', largeText)\n\n// Object, which can contain both static and refs/getter functions\nctx.class({\n  'will-never-show': false,\n  'could-show': () =\u003e maybeShow.value \u0026\u0026 shouldShow.value\n})\n```\n\n#### `.style()`\n\nAdd static or reactive inline styles.\n\n```ts\nctx.style(key: keyof CSSStyle | CSSStyle | MaybeRefOrGetter\u003cCSSStyle\u003e, value?: MaybeRefOrGetter\u003cLimitedPrimitive\u003e)\n```\nExample\n```ts\n// Single reactive property using getter function\nctx.style('display', () =\u003e show.value ? 'block' : 'none')\n// Getter function returning a style object\nctx.style(() =\u003e ({\n  display: show.value,\n  width: `${width.value}px`\n}))\n// Static style object\nctx.style({\n  position: 'relative',\n  left: '10px'\n})\n```\n\n#### `.attr()` \u0026 `.attrs()`\n\nBind static or reactive attributes.\n\n```ts\nctx.attr(key: string, value?: MaybeRefOrGetter\u003cPrimitive\u003e)\nctx.attrs(data: MaybeRefOrGetter\u003cRecord\u003cstring, Primitive\u003e\u003e)\n```\nExample\n```ts\n// Single attribute\nconst dynamicName = ref('element-name')\nctx.attr('name', dynamicName)\n\n// Attribute object\nctx.attrs({\n  disabled: true,\n  inert: true,\n})\n```\n\n#### Attribute shorthands\n\n```ts\nctx.id(value: MaybeRefOrGetter\u003cPrimitive\u003e) // id attribute\nctx.disabled(value: MaybeRefOrGetter\u003cboolean\u003e) // disabled attribute\n```\n\n---\n\n### Conditional rendering\n\nUsed when we want to display / hide certain components based on a condition.\n\n#### `.if()`\n\nConditionally add / remove elements from the DOM.\n\n```ts\nctx.if(condition: MaybeRefOrGetter)\n```\nExample\n```ts\n// Inside .setup(() =\u003e {})\nconst display = ref(true)\nctx.nest(\n  button('Toggle').click(() =\u003e display.value = !display.value),\n  span('Now you see me').if(display)\n)\n```\n\n#### `.show()`\n\nWorks just like `if` but leaves the component in the DOM, but appends `display: none` if false.\n\n```ts\nctx.show(condition: MaybeRefOrGetter)\n```\n\n---\n\n### Lifecycle\n\nHooks which can execute code at different stages of component's life cycle.\n\n#### `.onInit()`\n\nExecutes provided callback function when the component is initialized. Before being mounted in the DOM.\n\n```ts\nctx.oninit(callback: () =\u003e void)\n```\n\n#### `.onMount()`\n\nFires the provided callback when the Component is mounted to the DOM.\n\n```ts\nctx.onMount(callback: () =\u003e void)\n```\n\n#### `.onDestroy()`\n\nFires the provided callback when the Component is removed from the DOM.\n\n```ts\nctx.onDestroy(callback: () =\u003e void)\n```\n\n---\n\n### Utilities\n\nThis set of utilities might be more useful for people who want to extend the functionality or build a library with Cascade.\n\n```ts\n// Mounts the selected Component to the DOM\nctx.mount(selector: string)\n// Destroys the component instance and removes it from the DOM\nctx.destroy()\n// Copies the current instance and returns a fresh copy. This component instance is not mounted in the DOM and should be used as a child component elsewhere.\nconst clonedEl = ctx.clone()\n```\n\n---\n\n## Components\n\nCascade divides all of its components into two groups\n\n- **normal**\n- **void** (can't have children)\n\nThere are a few custom components which extend the base functionality\n\n### Image\n\n```ts\nconst image = img(\"https://i.imgur.com/naMnD3H.jpeg\")\n// Has extra two custom attributes\nimage.alt(alt: string)\nimage.src(src: string)\n```\n\n### Input \u0026 Textarea\n\n```ts\n// The first argument is the input type\nconst text = input('text')\n// Extra attributes\ntext.type(inputType: string)\ntext.value(value: MaybeRefOrGetter\u003cPrimitive\u003e)\ntext.placeholder(value: MaybeRefOrGetter\u003cstring | undefined\u003e)\ntext.name(value: MaybeRefOrGetter\u003cstring | undefined\u003e)\ntext.required(value: MaybeRefOrGetter\u003cboolean\u003e)\n```\n\n```ts\n// No arguments provided during initialization. Otherwise shares all the extra props as `input`.\nconst textarea = textarea()\n```\n\n### Option\n\nUsed within the `select` component.\n\n```ts\noption(label?: string, value?: MaybeRefOrGetter\u003cPrimitive\u003e)\n  .value(inputValue: MaybeRefOrGetter\u003cPrimitive\u003e)\n  .selected()\n```\n\nExample\n\n```ts\nconst selected = ref()\nconst select = select(\n  option('John', 24).selected(),\n  option('Andrew', 81),\n  option('Honza', 111)\n).model(selected)\n// ref's value will be 24 as an option was preselected\n```\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdolanske%2Fcascade","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdolanske%2Fcascade","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdolanske%2Fcascade/lists"}