# Directives Directives are a syntax sugar for custom tags that adhere to a specific contract: they receive a `value` and (optional) `var` and return an object of attributes to spread into a tag. ## Details ### Basic Example ```marko <onLongPress/attrs() { console.log("hello") }/> <button ...attrs>Say Hello</button> ``` With the syntax sugar, this becomes: ```marko <button #onLongPress() { console.log("hello") }>Say Hello</button> ``` ### Full example ``` <abc #xyz=foo/> ``` Desugars to ``` <xyz/_attrs=foo var() { return _var() }/> <abc/_var ..._attrs/> ``` ### Bind syntax The directive desugars before the bind desugars: ``` <abc #xyz:=foo/> ``` After the directive desugars: ``` <xyz/_attrs:=foo ref() { return _ref() }/> <abc/_ref ..._attrs/> ``` Then after the bind desugars: ``` <xyz/_attrs value=foo valueChange(_foo) { foo=_foo } ref() { return _ref() }/> <abc/_ref ..._attrs/> ``` ### Implementation of a directive A directive is a tag that #### Example: `<on-visible>` ``` export interface Input { value: () => void; var: () => HTMLElement; } <intersection-observer callback(entry) { if (entry.intersectionRatio > 0) { input.value(); } } options={...} el=var /> ``` ## Core usage ### Style & Class ```marko <div #style={ color: "blue" }/> ``` ### [Controllable HTML Elements](https://hackmd.io/2rMF4DMfTAOD8flTUxE9EQ#Controllable-HTML-elements) ```marko <let/name = "Marko"/> <input #value:=name/> ``` ### [CSS](https://hackmd.io/XEY0MK2ySI2GFrsADhS-vQ) The `<css>` tag would be a transform, but its use as a directive would follow the same rules as other directives. ``` <div #css=`font-weight:bold;color:${color}`/> ``` ## Limitations/Downsides ### No arbitrary transforms, can only affect attributes - No `<div #if=condition>` - No `<ReactComponent #react/>` ### Custom change handers are awkward ```marko <let/name = "Marko"/> <input #value=name #valueChange=console.log/> ``` Can use an intermediate `<let>` 🤮 ``` <let/intermediate value=foo valueChange() { ... }/> <abc #xyz:=intermediate/> ``` > Didn't we need this anyway for controllable components? [name=Luke] ### Composition In some ways this is better than the original proposal, but the directives can kinda leak up the tree If someone exposes a select like: MySelect.marko ``` <select ...input> <option>My Option</option> </select> ``` You can then do the following ``` <MySelect #selectedValue:=xyz/> ``` ### Name conflicts Many names that would be used for these directives are very likely to conflict with other names: - `#style` conflicts with the native - `#class` conflicts with the Marko 5 Inline Class Components - `value` seems like a very generic name for a tag - etc. #### Potential solution `#style` => `someprefix-style`/`style-attrs`/etc. ### Attribute conflicts If two directives return the same attribute, implemented as a normal spread, the last one wins, but that's maybe not the best thing? Do we want to have some sort of merging logic? Probably too many edge cases to realistically do that... #### Example ``` <div #class={ foo: isFoo } #css=`color:blue;`/> ``` In this hypothetical example, both `#class` and `#css` would provide a `class` attribute to spread into the `<div>`. With a normal spread, only the `class` from `#css` would be present. #### Potential solution Handles space-separated attrs like `class` (would work for `style` too). And event handlers. ```javascript function mergeAttr(name, a, b) { const aType = typeof a; const bType = typeof b; if (aType === bType) { if (aType === "string") { return `${a} ${b}`; } if (name.startsWith("on") && aType === "function") { return function(...args) { a(...args); b(...args); } } } return b; } ``` ## Alternative Names for the concept - Hashtags - Ghost tags 👻 - Attribute tags 😅 ## Alternative names for the passed tag variable getter - `ref` - `var` - `for` - `with` - `get` ### Would be nice... In some ways, if hoisting didn't require getter functions. ## Prior Art - [Svelte Actions](https://svelte.dev/docs/element-directives#use-action) - [Solid Directives](https://www.solidjs.com/docs/latest/api#use___)