QHTML7 specifications prompt. -------------- This section is fully implemented ----------------- --------------- use for reference only ---------------------- --------------- proceed to next milestone ------------------- 1. `qhtml.js` is included which will have the code to initialize the qhtml7.wasm file (which will be discussed later) using the glue `qhtml7-glue.js` which will be compiled as a type of webassembly based library that will provide the base classes and functionality for some parts of the system. We will discuss the webassembly details shortly. 2. Once the webassembly is loaded and initialized fully, we need to create a CustomElement class in javascript that has an attribute "ready" which is automatically set to 0 when the script runs in a closure. This CustomElement will be tag "q-html" so it will be accessible via . As soon as the element is created either via HTML or via Javascript .createDocument, the `ready` attribute is set to `"0"` and then the entire inner Contents of the element is stored and saved to a bound property on this particular DOM element under the name `.qhtmlSource`, accessible to all via `querySelector("q-html").qhtmlSource`. 3a. The next step, once the source is captured will be to clear the entire inner contents of the element leaving just an empty element with the .qhtmlSource property and an attribute that is "ready" set to "0". Now, the CustomElement will pass the source code to the WebAssembly QHTML7 parser. There will be a base class called QHTMLASTNode() which has the functions to scan arbitrary text as public methods for different shapes which all other AST-related objects will inherit methods from. This QHTML7 parser will first create a new QHTMLAstNode() which in the constructor will access the method to scan the text using AST parsing to break it down into different shapes. QHTMLAstNode will have a member which is a typed as `QHash` named `astChildren`. Any shape found that is not inside of brackets matching any of the following shapes shall be instantiated as a new class which will inherit the QHTMLAstNode base class and then added in order to this 'astChildren' QHash using index-based storage. 3b. The first shape to scan for will be constructs that contain this format: `[#id][.class1][.class2][,[.class2a][.class2b]][,[#id3][.class3]][,[.classN][#idN]] { }` This is a basic anonymous object definition. Anytime this exact style is used, it is for creating anonymous HTML elements, and if #id is attached to it in any way (optionally), that becomes the "ID" attribute which can be used to access via `querySelector("#id")`. THe same goes for .class1, .class2 etc if they are added (optionally) to the then that becomes the class attribute so `div.class1#ok { }` translates into an AST tag that will become an object class of QHTMLAstAnonNode(word, attributes, ). The AST parser will have numerous classes used internally to identify different types of patterns, this is a simple one. The new QHTMLAstAnonNode class inherits from QHTMLAstNode so that when its constructed, it will automatically scan/parse any arbitrary text inside and populate its own .astChildren QHash file (inherited from base class QHTMLAstNode). Providing a `,` (comma) will create a nested object, so `div,span { }` would create one `QHTMLAstAnonNode("div", QVariantList(), "span { }")` which would then parse out `span { }` into another node. Infinite commas can be applied to create deeply nested hierarchies like div,span,table,tr,td { text { hello world } } which would make QHTMLAstAnonNode with 5 descendants. You cannot place a named component inside of a comma chain of anonymous elements, typed QHTMLAstTypeNodes must be in a separate declaration separate from any QHTMLAstAnonNode objects with their own set of { } afterwards. QHTMLAstTypeNodes follow a slightly different pattern, which will be explained next. Also if the node is html { } or text { } then it is a special case node and can be included at the end of a comma separated chain of QHTMLAnonNode entries, but cannot appear in the beginning or anywhere in the middle of such a chain. text {} and html {} must be immediately followed by a {. 3c. The AST Parser's next pattern to look out for will be called QHTMLAstTypeNode, which will also inherit QHTMLAstNode to allow deep parsing. This class will be also defined in the Webassembly code as a class and used internally for the AST parser just like QHTMLAstAnonNode. This typed object will follow the same rules, the only difference is that it follows this syntax : [#id][.class1][.class2] { } where type-specification for now can be any individual word [a-zA-Z_\+\-]+ but not two words and cannot contain [#.*$] The constructor will be like this new QHTMLAstNamedTypeNode(, , attributes, innerText) and the object will be added to .astChildren once created. May have to use some form of type casting to store properly as QHTMLAstNode after instantiation as a different class. 3d. Now, once all text has been parsed in this fashion into QHTMLAstNode*-inherited classes. Now we will be able to walk this QHTMLAstNode tree that has been created using a public method which will basically be recursively walking the .astChildren hashes. Once everything is in place and all AST nodes are fully instantiated and stored, we must begin building our QHTMLContext tree. The first step is to call .enumerateKeywords() method on the top level QHTMLAstNode object. This function essentially goes through each entry in its own `.astChildren` QHash, and calls .astType() which will return either "QHTMLAstNamedTypeNode" or "QHTMLAstAnonNode" which will be implemented in the class definitons for each. If its a "QHTMLAstAnonNode" based on .astType()'s return value, then the .qhtmlKeyword property will be set to "" in its constructor. If the type is QHTMLAstNamedTypeNode, then we call .qhtmlType() on it which will return the .qhtmlKeyword property on the QHTMLAstNamedTypeNode (defined in the constructor initialization). While still in the .enumerateKeywords() method, we will populate a QHash shared by all QHTMLAstNode* classes named .astChildrenUUIDs which will generate a UUID for each .astChildren object and match the indexes to the .astChildrenUUIDs hash. Next, we will populate another hash table called .astChildrenUUIDKeywords that will contain the UUID as the key and the result of .qhtmlKeyword property for the matching .astChildren object based on the index / UUID data. There will be a helper method for QHTMLAstNode* classes which will be able to lookup the UUID by id, or by any QHTMLAstNode* inherited child that lives in .astChildren. 3e. Now that enumeration is complete, we scan the .astChildrenUUIDKeywords hash table on this object only looking for "q-keyword" types. Any keywords with `q-keyword` in them will populate cause the .qhtmlName property (stored in constructor of QHTMLAstNode* inherited classes) to be read and then the .qhtmlContent property (arbitrary inner source which is stored in constructor of QHTMLAstNode* inherited classes). If .qhtmlName is not null and .qhtmlContent is a single word, then we call the method this->updateKeywordReference(qhtmlName, qhtmlContent) which will modify the QHTMLContext object which is a public instance on all QHTMLAstNode*-inherited classes called .qhtmlNode which will be instantiated in the constructor. Calling updateKeywordReference(qhtmlName, qhtmlContent) on a QHTMLNode*-inherited class will modify a QHash called .qhtmlReferences which is key -> QHTMLReference* which is essentially the base class for all QHTML elements which can be accessed directly. More on this later. All we need to know for now is updateKeywordReference(QString, QString) on QHTMLNode, and that class will handle the rest. Next, for each .astChildren element, also call a method to trigger updateKeywordReference(qhtmlName, qhtmlContent) on all .astChildren objects so that the keyword update is also applied to all descendants. We also want to call updateKeywordReference for any of the default language keywords that are not modified by a q-keyword by setting updateKeywordReference("q-component", "q-component") etc for each of the language keyword-level commands. 3f. Now that we have our keyword references setup, we can use the .qhtmlNode->resolve() method from QHTMLNode which will look at the QHTMLNode's .qhtmlReferences hash table and return the correct QHTMLReference* which can be a QHTMLNode* or QHTMLKeyword* (or QHTMLProperty, QHTMLSlot, QHTMLSignal, QHTMLClass, QHTMLVar, QHTMLArray, QHTMLMap, QHTMLComponentDefinition, QHTMLComponentInstance, QHTMLTemplate, QHTMLScript, QHTMLModelView, QHTMLFactory, QHTMLMethod, QHTMLHTMLFragment, QHTMLTextFragment, QHTMLSourceFragment etc. more on this later) All of these types will inherit QHTMLReference so they can be stored in the .qhtmlReferences QHash and will have .qhtmlType property that returns what they are. 3g. Now we setup the references for all of the other QHTMLAstNode* elements, by enumerating the QHTMLAstNamedTypeNode objects and giving them .qhtmlUUID properties and .qhtmlName property and then sending a .updateNamedReference(qhtmlName, qhtmlUUID) so that all descendants will recognize the .qhtmlName value as UUID from .qhtmlUUID value. 3g. We now have the basic framework where we can store any of the different QHTML types of objects into a single data structure. Once our data structure is built, we can create the QHTMLDomTree which is a class all its own which will be populated via a method .loadFromAST() that will grab the .toQHTMLNode() on the root node, which will return the QHTMLNode object itself, which should be populated with all of the children QHTMLNode* data already. We will do a deep copy here and forget the QHTMLAstNode once the root node and descendants are copied over into the QHTMLDomTree. 3h. Now that we have our QHTMLNode setup with our keywords and named references added to QHTMLNode::resolve() which will find the object based on the .qhtmlReferences hash. This hash will have shallow reference pointers to objects which will be parented elsewhere, but their pointers shall remain valid and bound to their UUID through a series of link QHash tables throughout the QHTMLDomTree and its .qhtmlChildren. Care must be taken not to iterate over elements that have already been iterated over, for example, if there is an object defined as QHTMLComponentInstance myInstance = new QHTMLComponentInstance(); myInstance.qhtmlUUID = "20281-af322r"; Then when enumerating, we would skip enumeration by adding "20281-af322r" to a list of already enumerated objects. Also when setting up the context, we want to skip anything that already exists in the context, like if "20281-af322r" matches a QHTMLComponentInstance in `context` and is a member of .qhtmlChildren, then we would not iterate over it again to avoid endlessly looping over QHTMLNode* objects when setting up references since the references for that QHTMLNode* object would already have been resolved. When all is said and done, QHTMLDomTree* root should have .qhtmlProperties, .qhtmlUUID, .qhtmlChildren with various different QHTMLDomNode*-inherting objects, which each of them will also contain their own .qhtmlChildren. There should also be a .qhtmlContext which will have pointers to all parent QHTMLContext references as well as any defined directly in this QHTMLNode* (but not in the children node). There should be .parent() and .children() which return the .qhtmlParent QDOMNode pointer and iteratee over thee .qhtmlChildren QHash returning a QVector list of pointers to all children. There should be a .qhtmlContext which contains strings and pointers as well as a .resolve(string) that matches strings to references, and a .runtime() method that will be per-class type, so QHTMLComponentInstance will have a different .runtime() and QHTMLModelView's .runtime() and so on for every class that inherits from QDomNode. The runtime() will be called once all of the objects are fully parsed into the QDomTree() in order by executing the runtime() for an object, which will determine the next actions to take whether it be calling runtime() on children, or implementing some special syntax from the child nodes or whatever. One last thing, if the AST parser finds a block that does not necessarily match the named type or anonymous type, it should treat that text as a QHTMLUnknownFragment and store it as such. These QHTMLUnkownFragments will be used by the runtime() for various purposes, as will the anonymous and named types. For example: If the parser receives `q-component mycomp { ` slot { test } div { text { hello world } } `} 3i. Step 3's system should be split between two files: qhtml_parser.hpp and qhtml_types.hpp where the types such as QHTMLNode, QHTMLKeyword are all in qhtml_types.hpp, and the QHTMLAst* classes are all in qhtml_parser.hpp. The public facing API will be bound to the Javascript environment side of WASM so that it is done by the custom class that does ``` var tree = new Module.QHTMLDomTree(); var parser = new Module.QHTMLParser(); tree.loadFromAST(parser.parse()); this.qhtmlDomTree = tree; ``` and you will be able to access this tree via .qdom() on that custom class or access it directly from .qhtmlDom. as `document.querySelector("q-html").qdom() or document.querySelector("q-html").qhtmlDom` Now we're ready to start implementation of the runtime system. Here's the examples of q-html language that is to be fed by the parser, along with brief explainations and some example output to guuide through the creation of the runtime. For anything that requires interaction with the HTML DOM, the `document` property from javascript should be exposed to QHTML's WASM side via a emscripten::val parameter in a method before parsing begins, and then any script that must be executed in the javascript world should be done by passing the script's contents as a QVariant to a function accessible from the javascript `document` object, which will then on the Javascript side turn the string into an evaluateable script and add all of the QHTMLNode qhtmlContext and other bindings first into the active javascript context through bindings or by setting properties with references to ProxyObjects or setting vars or whatever works and then exectuing the javascript with the full qhtml context of the QDomNode that the script has inherited from its ancestors, along with the `this` property bound to the parent where the script is exected from. Generally speaking, scripts are only used in handlers like onClicked { } but there are a few other places where they turn up so be vigilent. 4. So now the following should be supported: h1 { text { Hello QHTML } } p { text { Your first QHTML render is running. } } ``` Resulting HTML: ```html

Hello QHTML

Your first QHTML render is running.

``` ## 2. Core Syntax ### Elements and nesting ```qhtml div { h2 { text { Product } } p { text { Lightweight UI syntax. } } } ``` Resulting HTML: ```html

Product

Lightweight UI syntax.

``` ### Selector chains (creates nested elements) ```qhtml div,section,h3 { text { Nested } } ``` Resulting HTML: ```html

Nested

``` ### Class and id shorthand ```qhtml div#main.card { p { text { Card body } } } ``` Resulting HTML: ```html

Card body

``` Multiple selectors with shorthand: ```qhtml div#my-id.my-class,span.my-class,h2#id2 { hello world } ``` This is a milestone checkpoint. All the above cases must parse successfully as-is exactly and all variations of them as well before the next step can be tackled. ---------------------------- end of milestone ---------------------------- ------------------ begin next milestone -------------------- ------------------- q-components --------------------- ---------------- This section is complete ------------------------- 5. Next up we have to implement actual runtime classes for q-component in wasm. q-components work like this: First, you define the q-component and give it a name: ``` q-component mycomponent { div,span,text { hello world } } ``` This creates a new Custom HTML Element and type that can be used as a QHTML Named Type. Next you instantiate it ``` mycomponent object1 { } ``` The result is in the HTML DOM a new ``` ``` and also the QDomTree will have two new objects added to the .qhtmlChildren wherever these are defined in: a new QHTMLComponentDefinition("mycomponent") object in the QHTMLDomTree a new QHTMLComponentInstance() an entry into the .qhtmlContext of the qhtmlParent QHTMLNode*-inheriting object that the definition and instance were parsed from of (can be two different places) so that .resolve("mycomponent") will return the QHTMLComponentDefintion object and .resolve("object1") will return the QHTMLComponentInstance object for the parent and all descendants that are directly descendant from this object (meaning calling .qhtmlParent successively will eventually lead back to the place where these are defined in. Anything outside of the tree would have to dotwalk to use the definitions like ``` someObject.qhtmlParent.mycomponent object2 { } ``` which would create the same mycomponent object but from say an element that is has a common parent but is not descendant directly (sibling).``` Dot walking should be attempted to be resolved only when using a typed name style syntax with ` { }` and never should be resolved when using an anonymous style declaration like ` { }` This is to avoid conflicts with the .class and #id shortcuts. If something cannot be resolved through dotwalking, but part of the string can be, resolve up to the last available object and then throw an error saying "could not resolve "" from "" This will get us to the end of the instantiation section for components. when the following syntax works this section is complete ``` q-component mycomponent { div,span,text { hello world } } mycomponent object1 { } ``` Result: ```
hello world
``` -------------- end of section ---------------- --------------- next section --------------------- ----------------- Complete ---------------------- ---------------- signals, slots, and properties ------------------ 6a. First we need QHTMLFunction type which is triggered by having `function myfunc(parameters) { }` in a q-component like so ``` q-component mycomp { function dowork(message) { alert(message) } } mycomp object1 { } /* object1.dowork(message) will cause the alert with message to show when called from javascript blocks such as another function or from the javascript in the browser directly. */ ``` This means that any function QHTMLFunction needs to be converted to a bound property function when the DOM is created and the QHTMLContext must have every entry passed into the function context as well so that all keywords and named objects are available. 6b. Now we have our q-component runtime all setup nicely, its time to implement q-signal, which is a QHTMLSignal() type in C++. the QHTMLSignal() typed object are always connected to the a member of the QDomTree root that they are in called .qhtmlSignalBus which has C++ class QHTMLSignalBus(). This signal bus has one job receive a generic signal from C++ QHTMLSignal objects that contains the signal UUID, the QHTMLNode* UUID it was called from, and any parameters specified by the signal. What the QHTMLSignalBus also does is allow other C++ (or javascript objects) to connect to the .signalReceived(signal, sender, parameters) signal and then execute arbitrary code when a specific signal is received from a specific sender and have the parameters available. This is how it works under the hood, but from QHTML it is implemented in a much simpler way. signals can only be added to q-component definitions to make them available to all instances of the definitions or to individual instances, which will in both cases make a new .qhtmlChildren element of type QHTMLSignal with a UUID for the signal and the .qhtmlParent property as well as the .qhtmlContext inherited from the ancestors. Implementation is as follows: ``` q-component mycomponent { q-signal mySignal(name, message) } q-component othercomponent { function handleSignal(name, message) { alert(name + " said " + message) } } mycomponent object1 { } othercomponent object2 { } /* from javascript: object1.mySignal.connect(object2.handleSignal) */ /* now object1.mySignal("bob", "hello") will ultimately cause an alert in the browser to pop up about bob saying hello */ ``` Implement this completely and carefully as its a critical component so it must be created exactly right or it was cause issues for everything else in the futre of the project. ------------------------------------- Section completed ---------------------------------- ------------------------- Next section ------------------------ ----------------------- proerties, slots, and this ----------------------- 7. Now for the fun part -- q-component will need to support a `this` binding available to any any javascript function or other QHTMLScriptNode that exists. `this` should always point to the closest instance of a q-component.. for example: q-component mycomp { function sendAlert() { alert("hello world"); } function dothing() { this.sendAlert(); } } mycomp object5 { } /* object5.dothing() will cause sendAlert() to fire and send an alert with "hello world" */ /* these functions are already bound to the DOM, but `this` must also be bound in the javascript exection context */ /* only `this` to anything that is of type QHTMLComponentInstance or has component instance metadata (when its a DOM element) */ ------ 8. We must also support this syntax q-component mycomp { q-property myprop: function genericfunction() { reutrn this.myprop } } Examples: mycomp obj1 { } q-component acomp { q-property prop1: 24 q-property prop2: "hello" q-property prop3: obj1 q-property prop4: obj1.myprop q-property prop5: 24% q-property prop6: 25vw q-property prop7: 30px q-property prop9: obj1.genericfunction() /* makes prop9 whatever the return value is of genericfunction() */ width: 24% } acomp obj7 { } /* Results: /* --------- */ /* obj7.prop1 == 24 */ /* obj7.prop7 == 30 /* obj7.prop6 == 25% of viewport */ /* obj7.prop5 == 24% of 0 since there is nothing to multiply 24% by */ /* obj7.width == 24% of the width of the parent DOM element stored as `style="width: 24%;` -------------------------------------------------- end of section ------------------------------------- ---------------------- next section ------------------------- ---------------------- inline handlers and event handlers ----------------------------- 9. Back ticks are supported and can be used with ${} in order to replace with resolved symbols inline. For example ``` q-component mycomp { q-property otherprop: "world" q-property myprop: `hello ${this.otherprop}` } mycomp obj5 { } /* obj5.myprop === "hello world" */ 10. Event handlers - in QHTML7 event handlers work much like how they work in regular javascript -- events (signals) are sent, and certain objects can subscribe to those signals so that when they fire, a javascript function will execute. It is also possible to use some syntactic sugar for implementating signal handlers on q-component-based objects (both instances or if defined in the QHTMLComponentDefinition, it will be applied to all QHTMLComponent instances as if it was manually added to each one as-is. References inside of signal handlers do not get resolved or evaluated until they are executed when receiving a signal. Format: q-component mycomp { onClick() { alert("mycomp was clicked") } } mycomp obj1 { } /* clicking on obj1 will cause the alert to fire */ /* other acceptable use cases */ mycomp obj1 { q-signal mysignal(name) q-property myprop: "hello" onclick { alert("no parameters will pass with no parameters") } onclick { alert(this.myprop + " all symbols are resolved and bound from the QHTMLContext of obj1"); } onmysignal(name) { alert("You can also bind to signals in this same way") } onCLICK { alert("this is case insensitive so ONclick, onCLICK or oNCLiCk all work the same."); } onmousepress(event) { alert("you can also pick up mouse press events and other javascript events that coming from obj1" + event) } onfocus { alert(`You can also back ticks with templated inline references that resolve inline ${this.myprop}`); } } ------------------------- 11. Using javascript primitive objects and arrays in QHTML. You can use primitive Javascript Arrays and Objects in QHTML as you would normally use them in Javascript, via { key: value, key: value } and [value1, value2, value3]. Care must be taken when using them as they can become confused easily with QHTML objects. The way to know the difference between the two is that when operating declaratively, the only places objects and arrays are allowed inline / anonymously is in a q-property assignment like this: q-component mycomp { q-property myarray: [25, 30, 35, 40] q-property myobj: { key1: 25, key2: 30, key3: 40 } } Under the hood, myarray is treated as a QHTMLNode with type QHTMLArrayNode and myobj is of type QHTMLMapNode. The QHTMLArrayNode is a QVector while stored in C++ side with helpers for .push() .pop(), .unshift() and .shift(). It also needs .concat(otherarray), .slice(), .splice(), .filter(), and .map(). These should all be evaluated when called by calling a function from the browser javascript context and rendering an javascript-style array from the values, then calling the javascript function specified and then feeding that back into WASM to generate a new array to output. I know its a bit of a performance botttleneck, but its maximum compatibility. QHTMLMapNode should work exactly the same way. If you can think of a better way to allow for arrays and objects from javascript to map cleanly with C++, then by all means do that instead as long as its fully compatible. -------------------------------------------------------- 12. html { } primitive tag. You can use a few primitive tags for different things: One of them is html { } which allows for direct insertion of pure HTML into the rendered output. Example: ``` div,span { html {
hello world
} } ``` Results in the HTML ```
hello world
``` --------------------------- ---------------------- 13. QHTML7 supports onPaintBackground(properties) { }, onPaintBorder(properties) { }, and onPaintMask(properties) { } special event handlers for q-components. These special handlers are for CSS houdini painting of backgrounds, borders and masks. The way they work is like this: WHenever one of the special handler are found, then internal javascript plus whatever QHTMLContext objects exist from the parent / ancestors are passed as a BLOB and registered as a paintWorklet with a unique ID and unique CSS class. Then the unique class is added as a style property to the DOM element when generated into DOM as `background: paint()`, or `border-image-source: paint(...)` or `mask-image` / `-webkit-mask-image`: paint(..)` CSS Painters also support properties being passed via CSS properties to manipulate the way the painting is done dynamically from outside of the painter event handler. This is done by specifying a list of QHTML Property references to the properties parameter on the onPaint*(properties) event handler. If any of the properties array is not of type QHTMLProperty, an error is shown in the console and the painting does not proceed. If all properties are of type QHTML Property, then each of the properties are added by name as CSS properties available to the paint worklet, and whenever they are changed by manipulating the actual QHTML Property* object from any source, which then triggers the onchanged(val) { } special handler to fire, and that is connected to a generated event handler in the browser side DOM which updates the CSS property by manipulating the .style property of the DOM element. Inside special paint nodes `this` is bound to the getContext2d() function, not the QHTMLComponentInstance, but the properties that are passed via `properties` are bound by name to the actual properties. Example ``` q-component paintedcomp { q-property color: "black"; q-property width: 240px; q-property height: 140px; q-property paintvars: [this.color, this.width, this.height] onPaintBackground(paintvars) { this.clearRect(white); this.setFill(this.color); this.drawRect(0, 0, this.width, this.height); } } ``` 14a. Styles and Themes. QHTML Supports a robust style system which allows for users to define groups of CSS styles and assign them as named objects which can be accessed from within the QHTML runtime. ``` q-style style1 { border: 2px solid green; color: white; width: calc(30px - 100px); } ``` 14b. Also supported is adding CSS classes from existing class-based CSS frameworks directly into styles. ``` q-style style2 { q-style-class { w3-row jumbo heading } padding: 4px 12px 6px 3px; } ``` 14c. q-style objects can be applied directly as an element class using the anonymous class syntax like so: ``` style1 { div,span { mycomponent { } } } style2 { span,h2 { text { hello world } } } ``` or combined together.. ``` style1 { div { style2 { span { text { hello world } } } } } ``` or used in a chain ``` style1,div,span { text { hello } } ``` The resulting effect either way is that all elements that are children of the style element get the .style property set with the CSS properties within that style and any class assignments. regardless of depth. 14d. If you want more fine grained control over which elements are styled, then you would use `q-theme` which allows you to choose CSS selectors and assign styles based on them. ``` q-theme mytheme { div { style1 } span { style1 style2 } h2 { style2 } .someclass > div { style1 style2 } } mytheme { div { text { abc } } span { text { hello world } } h2.someclass { text { headline } } } ``` There is also a q-default-theme which is an overidable set of colors that when a different theme is applied will simply "step aside" and let any q-theme definitions have precedence, this is useful for reusable components that you wish to customize part of without having to manually edit the whole component, implement a complex style property system, or completely re-style the entire thing. Its also necessary because in a reusable component, the q-theme would come directly before the actual component itself, and therefore would require styles to be applied to it per instance, and this could not be. So ``` q-default-theme mydefault { .class1 { style1 style2 } } mydefault { div.class1 { } } ``` And you can also combine multiple q-themes together by including a q-theme into your theme ``` q-theme mytheme2 { q-child-theme { mytheme1 } span { style2 } } ``` The order that the items appear in the theme control the final result with the exception of q-default-theme which always allows q-theme to take precedence when multiple styles are conflicting. 14e. q-transition. A q-transition is a named transition object that can be applied directly to a QHTML element or component instance, or from inside a q-style. ``` q-transition fadeMove { duration { 800 } timing { ease-in-out } delay { 0 } } div { fadeMove { opacity color paddingTop } } q-component panel { fadeMove { opacity } } panel { } ``` When a transition is applied directly to a component instance, assignments such as `panel.opacity = 0.4` write through to the component host element's CSS style. Camel-case CSS shortcut names such as `paddingTop` are converted to their CSS property names when the transition is installed. The previous q-style compatibility syntax remains supported: ``` q-transition fadeOnly { property { opacity } duration { 800 } timing { ease-in-out } delay { 0 } } q-style fadingSurface { q-style-transition { fadeOnly } } ``` ------------------ completed ------------------------------- ---------------------------------- end of section ---------------------------------------- ------------------------------ next section -------------------------- 15. slots. QHTML QHTMLComponentDefinitions must have a way to inject arbitrary QHTML into them or having a QHTMLComponentDefinition would be no better than just defining a static Custom Element. To inject arbitrary HTML into specific place of a QHTMLComponent we will have a QHTMLComponentSlot object added to the QHTMLComponentDefinition. The declarative format is as follows: original style - kept for backwards compatibility: ``` q-component mycomp { div,span { slot { slotname } /* slot declaration */ q-slot-default slotname { text { default contents when not specified } } /* set defaults for a specific slot */ } } ``` or we can declare the slot as a named type and then provide a default QHTML Fragment to display when the slot is not used. new style - only QHTML7: ``` q-component mycomp { div,span { slot slotname { text { default contents when not specified } } } } ``` Both are correct, the first format is for backwards compatability, while the 2nd is only compatible with QHTML7. This creates the QHTMLComponentSlot object in the QHTML Dom Tree as a child of whatever the closest QHTMLComponentDefinition is, even if its not a direct parent, we want to go to the closest QHTMLComponentDefinition object always for ownership but keep the slot's position in the tree - ill explain why.. In order to use these slots, you would provide the QHTML Fragment to when creating a QHTMLComponentInstance like this: ``` q-component mycomp { div,span { slot slotname { text { default contents when not specified } } } } mycomp object1 { slotname { div,span,text { heres the injected slot } } } ``` This yields the following being rendered: ```
heres the injected slot
``` Where we have mycomp and then the slot is transformed into the injected text and replaced in the Instance with the QHTMLFragment and then parsed normally inheriting the context of the instantiation and its ancestors and the definition's QHTML Context as well, taking the instance over the definition's named types based on which one was created last. Slots can be accessed programatically through QHTMLComponentInstance.slots() to get a list of QHTMLComponentSlot objects and you can inject into any of them as well as append, replace, or otherwise modify the inner contents by modifying the .qhtmlChildren object from any QHTMLComponentSlot object which will be populated when the QHTMLComponentInstance is created and populated. --------------------------------------- -------------------- next section --------------- 16. QHTMLTimer - QHTML supports a usable timer with various features. It is accessed using this format: ``` q-timer mytimer { ontimeout() { /* do javascript code here */ } interval: 400 running: false repeat: true } ``` Setting mytimer.running to true will start the timer. You can also call mytimer.start(), mytimer.stop() and change other properties like mytimer.interval = 500; QHTMLTimer has a QHTMLSignal built in: timeout() which is fired on each timeout. Under the hood QHTMLTimer uses the QTimer C++ implementation with either QTimer::singleShot or a QTimer object and related properties / methods. The QTimer is never exposed to the public API, and is internal to the QHTMLTimer class. It inherits context from its parent and ancestors like any other QHTML object. Timers should not create a `this` entry in their QHTMLContext for event handlers, instead `this` should be inherited from the parent object when used declaratively. --------- ------------------------------------------------- end of milestone 2 ------------------------------------------ ------------------------------------- begin milestone 3 --------------------------------- 17. Comments and q-class. Comments should be ignored when used anywhere when parsing QHTML.. When /* is detected, evyerthing should be skipped until a */ is detected, which will signal the end of the comment. Do not create objects from comments. 18. q-class is a special type of object. While it is defined and parsed in QHTML C++ on the WASM side, it follows different rules than the rules we have seen thus far. q-class is something that will be forward to the browser javascript and become a javascript class with methods, and the standard javascript syntax. Example: q-class myclass { myclass(param1, param2) { this.myvar = 24; this.othervar = param2; } function doWork() { this.myvar = this.othervar; alert(this.myvar); } } The difference between a q-component and a q-class is that a q-class has a constructor and can have assignments directly bound to it since it will live primarily in the javascript browser context rather than the wasm context. In fact, it should be converted pretty much as-is except for the constructor which needs to be in the javascript class format. But it should be more or less shipped as-is to javascript and then created into a javascript class object where it will serve the purpose of interaction with the DOM using dynamic javascript-style interactions, but it will inherit the QHTMLContext of the parent objects as well which will be converted into actual bound properties that point to real objects before sending it to the javascript side so that it is able to resolve all of the objects from the QHTML Context and access those objects, but primarily be a javascript mechanism useful for doing things like instantiating dynamic HTML content or javascript-powered objects and then calling functions in the q-class from the wasm side and vice versa. Its essentially a bridge between the two which allows for javascript style class logic with QHTML object access. If you have any trouble with figuring out how to implement this, do not guess. Please stop and present your situation that you cannot resolve without massive implementation. This will be a QHTMLClass-typed object in the QHTMLDomTree. Example usage: ``` q-class StressRect { StressRect(stage, index, config) { this.stage = stage; this.index = index; this.config = config; this.running = false; this.animations = {}; .... } } q-class StressTest { StressTest() { this.config = { boxCount: Number(new URLSearchParams(window.location.search).get("count")) || 10, maxSize: 100, duration: 2000, properties: [ "width", "x"] }; this.host = document.querySelector("#stress-root"); this.board = document.createElement("div"); this.board.className = "stress-board"; this.status = document.createElement("div"); this.status.className = "stress-status"; this.status.textContent = "Creating rectangles..."; this.board.appendChild(this.status); this.host.appendChild(this.board); this.rects = []; for (var i = 0; i < this.config.boxCount; i += 1) { this.rects.push(new StressRect(this.board, i, this.config)); } this.status.textContent = "Created " + this.rects.length + " StressRect q-class instances"; this.start(); } function start() { for (var i = 0; i < this.rects.length; i += 1) { this.rects[i].start(); } } function stop() { for (var i = 0; i < this.rects.length; i += 1) { this.rects[i].stop(); } } } StressTest stressRoot { } ``` ---------------------------------- complete -------------------------- ---------- 19. q-property-animation (QHTMLPropertyAnimation in the QHTML DOM Tree) is a wrapper for Qt's QPropertyAnimation that creates a new instance of a QPropertyAnimation under the hood with a temporary object and variable since Qt's property system cannot have new properties defined at runtime, we will use like "x" property of a new QObject. Then once the QObject is created say as i_object, we will do i_object->connect(QObject::xChanged, this, QHTMLPropertyAnimation::i_handleXChange) which will track x's value as it animates to determine where in the process the animation currently is relative to the `from` property and the `to` property and have a precalculated list of values which will corrospond to step numbers by using the total duration property, the delta between the `from` and `to` properties, and then dividing the delta up by the `steps` property and then storing the resulting number as the amount of change per step stored as `stepAmount` and create an array by starting at 0, and then adding the `stepAmount` value to the number and adding the resulting number to a QVector called i_step_stones; Then, when the animation is running, if the stepAmount is not 0, and (if x is lower than currentStep * stepAmount and x is positive) or (x is greater than stepAmount * currentStep and stepAmount is negative), then we do nothing. If (x is greater than currentStep * stepAmount and stepAmount > 0) or (x is less than currentStep * stepAmount and stepAmount < 0) then we emit the QHTMLSignal stepped(x, currentStep) and then do currentStep++; The q-property-animation will also connect to the target via QHTML connection so that its `stepped` signal will update the target property with x (temporary property) 20. for loops in QHTML -- QHTML supports the syntax: ``` q-property somearrayRef: [20, 30, 40, 50] for (somename in somearrayRef) { div,span { text { ${somename} } } } ``` this effectively creates the equivalent of ``` div,span { text { 20 } } div,span { text { 30 } } div,span { text { 40 } } div,span { text { 50 } } ``` but stores it as a QHTMLForNode and adds all QHTML Fragments as its .qhtmlChildren, which parses them all once the loop completes and turns all of the resulting QHTML into objects in the QHTMLDomTree individually but keeep in their metadata the UUID of the QHTMLForNode on each of the created children then move them outsidde of the for loop (directly afterwards) meaning they should be inserted into the QHTMLForNode's QHTML parent immediately after the for expression. Creation of a for loop like this should also create an event handler for the container being iterated so that whenever the container changes a value (in this case its an array but might support others in the future like iterating over key/value pairs), it will inform the QHTMLForNode of the change via QHTML Signal and then remove all of the elements which match the UUID of the QHTMLForNode in their metadata and re-evaluate the entire loop and creating the qhtmlChildren etc and rendering the new output. This will prevent stale data and make it more reactive. ---------------------------------------- 20. Next is q-import and q-require. The way q-import works is that it tries to import from the URL using fetch from javascript to get the contents of the file (must be a file with QHTML unparsed syntax). Once the contents have been successfully retrieved, then the contents of the file will be parsed into the QHTML Dom Tree in C++ as if the contents of the file were inline. For this reason, all import statements must either be resolved in a blocking manner before the rest of hte page loads, or the parser must re-evaluate each time a q-import completes to scan for any new q-components or other types of QHTML definitions that can impact waht the different words mean. (Example defined q-component or q-keyword will change the fundamental structure of the other content). The second option is the q-import route so that the page can load partially while fetches happpen async, but either way is fine. q-require is for blocking any parsing until all q-require imports are completely parsed and rendered in the QHTMLDomTree and in HTML if applicable. q-import/q-require also has a cache feature to store the contents of the fetch along with the current version number of the script in a base64 string that the parser can decode and then parse instead of doing another fetch. The two commands have the same syntax and options, just different behavior. q-require will never cause a re-parse of a page, it will wait until all q-require finish before parsing the entire page. q-import will not cause a re-parsing either of the entire host, but will re-parse any objects that currently are resolvable from the context where the q-import happens. So if you call q-import with the parent being the host q-html root, then the entire QHTMLDomTree will be invalidated and re-parsed. QHTMLNode* elements which are not affected by either a q-keyword or a q-component definition meaning none of their qhtmlChildren or the object itself has no references to anything matching the keywords or objects specified, then that node is just serialized from the old QHTMLDomTree and unserialized in the new QHTMLDomTree. For this we need .serialize() and .unserialize(string) which will enumerate the entire QDomTree as into a QDataStream that can be read back. The easiest way to do this is probably to implement a data structure on QHTMLDomNode* that just makes a string with or even a hash with all the values in it. Do not use JSON on the C++ side for this. If desired, serialization can be skipped if the values can be safely copied between the trees via some other means. syntax: ``` q-import { path/to/file.qhtml } q-import { https://site.com/file.qhtml } q-import { :/components/file.html } /* accesses internal resorource files compiled into the WASM binary */ q-import { somefile.qhtml cache } /* cache the result along with the version of the script to avoid extra fetch */ q-import { somefile.qhtml nocache } /* send fetch request and ask not to send cached responses at all (for dynamic backend generated .qhtml files) */ q-require { path/to/file.qhtml } etc.. ``` ----------------------------------------------------------- 21. q-painter -- q-painter is essentially a CSS houdini javascript worklet rolled into a Javascript class that can be loaded as a blob and then added using the CSS houdini paint API. It is equivalent in functionality to the onPaintBackground, onPaintMask, and onPaintBorder commands, with the difference of that it can be applied to multiple different paint contexts using either q-style + q-theme or by specifying it inside of an onPaintBackground, onPaintMask, or onPaintBorder command. Additionally, q-painter is parsed into a QHTMLPainter object, so it can be accessed / saved as a q-property or passed into different contexts such as q-canvas onPaint event handlers. (More on q-canvas in in section 22). QHTMLPainter is parsed using this format / syntax: ``` q-painter mypainter { onpaint { this.fillStyle = "#2563eb"; this.fillRect(0, 0, this.width / 2, this.height); this.fillStyle = "#f97316"; this.fillRect(this.width / 2, 0, this.width / 2, this.height); } } q-style mystyle { q-style-painter { background { mypainter } } } q-theme painter-theme { .paint-target { mystyle } } painter-theme { div.paint-target { style { width: 240px height: 110px } text { Painter target } } } ``` It is also possible to simply reference a QPainter inside of a q-canvas or paint event handler like this: ``` q-canvas mycanvas { onpaint { mypainter { } } } ``` or you can use it inside of onPaintBackground { }, onPaintMask { } or onPaintBorder { } in the same way ``` div { onPaintBackground { mypainter { } } } ``` All QHTMLNode* elements which render anything into the HTML DOM must support onPaintBackground, onPaintMask and onPaintBorder event handlers whether they are just plain HTML, or q-components with HTML contents in them. ---------------- 22. q-canvas is a implementation that uses the onPaint { } event handler to allow for free painting by using the context property which is automatically set to getContext2d(). ``` q-canvas mycanvas { width: 50vw height: 50vh onPaint { context.fillStyle = "#2563eb"; context.fillRect(0, 0, this.width / 2, this.height); context.fillStyle = "#f97316"; context.fillRect(this.width / 2, 0, this.width / 2, this.height); } } ``` * Note* if you specify the width or height (or anything really in QHTML) in CSS units like 50% or 100vh, they are converted into pixels when getting the value of those properties by performing the necessary calculations to determine what the exact pixel size is of the value you specified. When accessing this.width and this.height, they will return a single integer regardless of what type of unit you put in there, and if the dimensions change, some of those units will automatically re-adjust and return new values depending on the nature of the change and the units you choose. This can cause flickering or re-painting calls to happen in environments that frequently adjust the layout and dimensions or when animating objects containing a q-canvas inside of it. ------------------------------------------------ 23. Next thing we need is this: q-state-machine, and q-state, which will be represented as QHTMLStateMachine and QHTMLState. Each QStateMachine will be comprised of multiple QHTMLState objects for its children. Each one will have an assigned name when instantiated, so they must be named types, never anonymous. ``` q-state-machine mymachine { q-state state1 { div,span,text { helllo world } } q-state state2 { div,span,h2,text { testing } } ... } ``` To activate a state you set the state property to one of the states via name. So, ``` button { onclick { mymachine.state = mymachine.state1; } text { toggle state 1 } } ``` It is possible to setup a state outside of a state machine as long as it can be resolved (ie is in the QHTML Context where the expression is called from. CHanging a state will effectively update the QHTML that is rendered both in the DOM and to runtime, so it requires update whenever the state changes along with re-parsing of the state's current contents and then rendering them into the DOM. There are optimizations possible here, but do not need to be done as of yet unless they are super simple to implement, which they probably are not. ------------------ 24. q-layout, q-row, and q-col -- q-layout is a special kind of object, created as a QHTMLLayout element, it inherits all context objects from the parent 's QHTML Context. QLayout also inherits the context all child .qhtmlChildren that are top level typed names inside of this particular q-layout or in a child q-row or child q-col. It also can access child q-row and q-col children of children and so on. It does not gain access to typed names inside of QHTMLComponentInstance or QHTMLComponentDefinition or other typed named objects, only the ones that are top-level names that are affected by the layout. QHTMLLayout is partially a base class that provides some functionality, but it is also a DOM element when rendered. QHTMLLayout has a few classes that inherit it in C++ -- QHTMLLayoutRow and QHTMLLayoutCol. QHTMLLayout provides the .addRow(QHTMLLayoutRow* row), .addCol(QHTMLLayoutCol* col) and .addLayout(QHTMLLayout* layout) functions. Since QHTMLLayoutRow and QHTMLLayoutCol inherit the class definition from QHTMLLayout, then can also add rows, columns, and layouts to themselves as well by calling .addRow or .addCol or .addLayout. QHTMLLayout also has the following hard coded properties: rows, cols as integers to specify the number of rows or columns in a particular layout area. Also supported is the width: and height: properties which accept bound properties via dot walking as values, CSS values with units attached (24vw), plain integers and float values. QHTMLLayout and friends (row and col) also must support .children() which will provide all of the non-layout child objects from .qhtmlChidlren added to a particular layout, row, or column. QHTMLLayout also supports setting the x and y properties just like all the other QHTMLNodes can as well as the full set of CSS properties that are already available like .backgroundColor, .paddingLeft, etc so that these layouts can be styled easily. The QHTML is like this: ``` /* make a 3x2 grid of the word "hello world" with blue background, 3 rows by 2 columns 250 pixels wide by 24vh tall. */ q-layout { width: 250px; height: 24vh; rows: 3; cols: 2; backgroundColor: blue; div { span { text { hello world }}} div { span { text { hello world }}} div { span { text { hello world }}} div { span { text { hello world }}} div { span { text { hello world }}} div { span { text { hello world }}} } /* make a 2x2 layout with a column of 1x3 items on the left and just a single div on the right */ q-layout { width: 100%; height: 100%; rows: 1; cols: 2; q-col col1 { /* left side */ div,text { item 1} div,text { item 2} div,text { item 3} } div,text { right side } } ``` layouts can be either named types or anonymous. Making them anonymous objects will render them in the DOM, but they wqill not be accessible to the QHTML runtime by name, only through the DOM via querySelector(). Layout-builder behavior: tools/layout-builder.html is the reference implementation for manipulating QHTMLLayout, QHTMLLayoutRow, and QHTMLLayoutCol objects from a browser tool. The QHTML view for this tool lives in tools/layout-builder/main.qhtml and the imperative editor logic lives in tools/layout-builder/main.js so the same builder can be embedded into tools/page-builder.html. The JavaScript editor must mutate the QHTML layout object model, preserve the user's declared units, and export by calling the QHTML serialization path instead of rebuilding large strings by hand. The layout editor owns an internal root q-layout wrapper that is not part of the exported QHTML. This root exists only inside the builder canvas, cannot be edited, resized, dragged, replaced, or deleted, and is used as the absolute top-level drop/context target. It must dynamically size to its children and provide extra canvas space, approximately 3-5vh/vw beyond the outermost child borders, so users can still add rows, columns, layouts, or QHTML around content even when the visible layout fills its current bounds. Saving or exporting strips this internal root and serializes only its user-created children. Default layout properties are inherit unless the user explicitly sets them. A new q-layout defaults to width: inherit, height: inherit, gap: inherit, and contains a q-row child. A new q-row defaults to width: inherit, height: 20vh, minWidth: 1vw, minHeight: 1vh, and gap: inherit. A new q-col defaults to width: 20vw, height: inherit, minWidth: 1vw, and gap: inherit. New rows and columns do not add placeholder `text { row }` or `text { col }` children; visual identification belongs to the layout tree/sidebar and selection outlines. Blank property fields in the layout editor are written back as inherit, not as 0px or auto. The layout editor includes a collapsible right-side structure tree. This tree is generated from the builder model, not from the rendered DOM, so it can show q-layout, q-row, q-col, and qhtml/palette content children even when those children do not have their own layout DOM element. Clicking a tree item selects the corresponding builder node. Branches can collapse independently. The tree is an editor affordance only and is not exported into final QHTML. The layout editor context menu includes a CSS submenu organized into grouped submenus: Colors, Size, Spacing, Border, and Text. These groups expose common shortcut properties such as backgroundColor, color, borderColor, width, height, minWidth, minHeight, maxWidth, maxHeight, gap, padding and padding sides, margin sides, borderWidth, borderRadius, fontSize, letterSpacing, and lineHeight. Color properties open a color picker dialog. Length properties open a number plus unit dialog. The editor may use pixels internally to calculate accurate conversion between px, %, vw, vh, vmin, vmax, rem, and em, but saving writes the selected unit back to the stored QHTML property. Cancel leaves the stored QHTML unchanged. The layout editor context menu operates on the deepest highlighted q-layout, q-row, or q-col under the cursor. When the menu opens, highlighting is frozen until the menu closes. Add -> Row, Add -> Column, and Add -> Layout each support Before, After, As Child, and Replace where the operation is valid. Columns can only be inserted as children into rows or layouts. Rows can only be inserted as children into columns or layouts. If a q-col is highlighted, Add -> Column -> As Child is disabled. If a q-row is highlighted, Add -> Row -> As Child is disabled. Edit opens the selected node's QHTML source in q-editor and Save reparses it into the selected object. Delete removes the selected layout object and its layout children. Dragging in the center of a highlighted layout object moves that object. Dragging near an edge resizes only the dimension represented by that edge. Resize values are allowed to use px internally for pointer math but must be converted back to the unit originally stored on the object: vh remains vh, vw remains vw, percent remains percent, and inherit remains inherit until the user explicitly gives the object a concrete size. Resizing must never convert persistent QHTML layout properties to px merely because the pointer event was measured in pixels. For q-col children of a q-row, horizontal resizing follows row packing rules. The first visible layout child of a q-row cannot be resized from the left edge. Dragging the left edge of any later q-col toward the left shrinks preceding q-col siblings, nearest siblings included in the shared reduction, while the selected q-col grows. Dragging that left edge toward the right expands preceding q-col siblings nearest-first while the selected q-col shrinks. Width changes must respect minWidth, maxWidth where specified, and the minimum width required by visible content. The editor must not use x offsets for this standard q-col-in-q-row resize case. Dragging the right edge of a q-col under a q-row grows the selected q-col and shrinks following q-col siblings when the row's right boundary would otherwise be exceeded. Following siblings are not given x offsets for this case either; their widths are reduced until their min/content width blocks further resizing. Columns that share the same q-row parent may not overlap. A q-col's left edge cannot pass the right edge of previous siblings and its right edge cannot pass the left edge of following siblings. When constrained, the resize operation must distribute shrink or growth across eligible sibling columns according to the side being dragged instead of creating overlapping layout geometry. Rows are treated differently because web pages can extend vertically. A q-row can always be resized vertically. If a q-row is the direct child of a q-col, its horizontal width is constrained to that q-col's width. Otherwise it may resize horizontally under the same general bounds as its parent permits. Dragging the top edge of a q-row upward shrinks previous q-row siblings until they reach min/content height. Dragging the bottom edge grows the selected row and pushes following rows downward. If a parent has an explicit height, the bottom-edge resize is blocked when following rows would exceed the parent unless the selected row is the last row sibling. If the selected q-row is the last sibling, dragging its bottom edge beyond the parent grows ancestor heights along the last-child path so the document can continue downward. Dropping a dragged q-row, q-col, or q-layout uses the current deepest layout object under the cursor and the side or center region of that target. A center drop prompts with a modal dialog, not a JavaScript prompt, and offers Swap, Replace, or Cancel. Replacing a node that has children must prompt again to delete children or move them into the dropped node. Row-to-row drops use top/bottom for before/after and left/right for inserting as a child at the beginning or end of the target row. Row-to-col drops use top/bottom to add as a child at the start/end of the target column and left/right to create a neighboring column containing the dropped row. Equivalent column operations follow the row/column insertion constraints above. ------------------------- 25. tools/page-builder.html -- functional overview. This is the dynamic page builder tool for QHTML7. The current shell embeds the shared layout builder as the main canvas and loads the default palette through: ``` q-import { page-builder/palette.qhtml } ``` The palette module is split into two files. tools/page-builder/palette.qhtml contains QHTML only and must contain q-component definitions only. It must not contain top-level rendered instances, raw JSON runtime data, or string-encoded component definitions as attributes. tools/page-builder/palette.js owns drag and drop events, registry state, slot migration state, and calls into QHTML APIs such as toJSON(), fromJSON(), toQHTML(), fromQHTML(), childList(), and findChildrenByType() as those APIs become available. The base palette type is: ``` q-component builderPaletteItem { } ``` Every concrete palette entry extends this type: ``` q-component mySpecialPalette extends builderPaletteItem { } ``` builderPaletteItem defines common metadata properties including paletteId, canvasInstanceId, definitionName, definitionUUID, displayName, category, description, iconLabel, instanceName, instanceQHTML, and slotNames. This component is metadata for the palette entry and should not be responsible for rendering the palette control. Rendered palette controls are builderPaletteButton instances. Each palette control must be placed inside a q-layout with a q-col containing exactly one builderPaletteButton for that palette entry. builderPaletteButton renders the fixed-size rectangular button, exposes the same metadata fields needed for display, and implements createInstanceFromType(componentDefinition). Concrete palette buttons override instantiatePaletteItem() and call createInstanceFromType() with the matching builderPaletteItem definition: ``` q-layout { q-row { q-col { builderPaletteButton mybutton { paletteId: "builder.hero" definitionName: "builderHeroBlock" displayName: "Hero" function instantiatePaletteItem() { return this.createInstanceFromType(builderHeroPaletteItem); } } } } } ``` createInstanceFromType() returns the newly created QHTML instance reference or, while the drag/drop canvas is being wired, the bridge-side instance reference that records the target component definition, palette id, generated canvas instance id, and available slot list. That returned reference is what later drag/drop code appends to qhtmlChildren or to a QHTMLComponentInstanceSlot. palette.js maintains the browser-side page-builder palette state: - paletteDefinitions maps paletteId to the QHTMLComponentDefinition name, definition UUID, and current slot list. - paletteItems maps paletteId to the rendered palette item component and its metadata. - canvasInstances maps canvasInstanceId to the QHTML component instance placed on the canvas. - topLevelCanvasItems stores canvas instances that are direct children of q-layout, q-row, or q-col objects. - slotPlacements stores components dropped into named slots of other component instances. - pendingSlotMigrations stores temporary content while a palette definition changes its available slots. Slot lists should be discovered from the actual QHTMLComponentDefinition whenever the definition object is available. The JavaScript controller can call definition.childList() and recursively inspect the returned QHTMLNode objects for children whose qhtmlType is QHTMLSlot, QHTMLSlotDefault, or QHTMLComponentSlot. If findChildrenByType("QHTMLSlot") is available, it can be used as the fast path, but childList() remains the canonical object-graph fallback. The explicit slotNames property on builderPaletteItem is only bootstrap metadata for rendered palette cards and should not be treated as more authoritative than the QHTMLComponentDefinition children. Palette IDs are stable editor identifiers. Definition UUIDs identify the component definition that a palette item instantiates. Canvas instance IDs identify each dropped instance on the page builder canvas. A palette item may create many canvas instances, so canvasInstanceId must never be treated as the same value as paletteId or definitionUUID. When a palette item is dragged, palette.js places a JSON payload on DataTransfer under application/qhtml-palette+json and also provides text/qhtml-palette-id, text/qhtml-definition-name, text/qhtml-instance, and text/plain fallbacks. The layout builder or page-builder canvas consumes that payload and creates a QHTMLComponentInstance from the referenced QHTMLComponentDefinition. If the drop target is a q-layout, q-row, or q-col, the new component is tracked as a top-level canvas item. If the drop target is a slot inside another QHTMLComponentInstance, the placement is tracked in slotPlacements by owner instance ID and slot name. Slot migration is required when a palette component definition changes. Before a QHTMLComponentDefinition is replaced, palette.js compares the old slot list to the new slot list. The preferred comparison source is the QHTMLComponentDefinition child graph: call childList(), find QHTMLSlot children, and compare their qhtmlName values. If any slots were removed, every canvas instance using that palette definition is scanned for slotPlacements using those removed slot names. The editor opens a migration dialog listing the removed slots and available new slots. For each affected placement the user chooses a new slot or Delete. During this dialog the old slot content is kept in pendingSlotMigrations. Only after every affected placement has a destination does the actual QHTMLComponentDefinition change happen. Then the stored content is appended to the new slot or removed when Delete was chosen. The page builder canvas should use the shared layout-builder rules from section 24 for adding, moving, resizing, and deleting q-layout, q-row, and q-col objects. Page content components are inserted into those layout objects as QHTMLComponentInstance children. The builder should prefer QHTML object APIs over string manipulation: inspect with childList(), findChildrenByType(), slots(), toJSON(), and toQHTML(); mutate with append(), remove(), setProperty(), fromJSON(), and fromQHTML(); then ask the QHTML runtime to re-render the affected object. Default palette components should be declared in palette.qhtml as normal q-component definitions. The palette item definition references the actual canvas component through definitionName and definitionUUID, while the canvas component definition itself is also declared in palette.qhtml. This keeps the component editable as QHTML, avoids storing large escaped QHTML strings in DOM attributes, and lets the same definitions be serialized through the normal QHTMLDomTree APIs. The default palette should cover common modern CMS site-builder constructs, not only primitive examples. Current built-in palette definitions include hero sections, decorated headlines, callouts, two-column content, feature cards, HTML embeds, FAQ blocks, pricing boxes, button rows, forms, customer reviews, stats bands, and logo strips. These components should use QHTML7 shortcut CSS properties for their default presentation and should avoid raw inline `style {}` blocks. 26. Additional implementation for page-builder 26a. First off we need to ensure that QHTMLComponentDefinition class has a .toJSON() or similar function which will convert the QComponentDefinition into a JSON object with a variety of properties. Properties will include: qhtmlName, qhtmlChildren and qhtmlInherits. qhtmlChildren and qhtmlInherits are JSON arrays, and qhtmlName is the accessible name that this QQHTMLComponentDefinition can be instantiated with. Since we are also converting the children of the QHTMLComponentDefinition into JSON as well, we will need to provide a .toJSON() as a unique method for every QHTML* type in C++. Each one must also follow a basic format so that they can be distinguished from each other. The basic format is this: For the following qhtml declarative code: ``` q-component othercomp { q-signal signal1(val) function method1(val) { alert(val); } q-property value1: 42 } q-component mycomp extends othercomp { q-property value2: 95 function method2(val) { this.method1(val) } div,span { text { hello world } b { text { ${this.value1} } } } onvalue1changed(val) { this.querySelector("b").innerHTML = String(val); this.method2(val); } } ``` `QHTMLDomTree().toJSON()` should return the following: `document.querySelector("q-html").toJSON()` should return the same: ``` [{ qhtmlType: "QHTMLComponentDefinition", qhtmlName: "othercomp", qhtmlChildren:[{qhtmlType: "QHTMLSignal", qhtmlName: "signal1", qhtmlParameters: ["val"]}, {qhtmlType: "QHTMLFunction", qhtmlName: "method1", qhtmlParameters: ["val"], qhtmlChildren: [ {qhtmlType: "QHTMLJavaScriptBlock", qhtmlContents: "alert(val);"}]}, {qhtmltype: "QHTMLProperty", qhtmlName: "value1", qhtmlValue: {type: "number", value: "42"}}]}, {qhtmlType: "QHTMLComponentDefinition", qhtmlName: "mycomp", qhtmlInherits:[ ], qhtmlChildren: [ , {qhtmlType: "QHTMLAnonNode", ..., {qhtmlType: "QHTMLEventHandler", ...}}] }] ``` This format will allow for quick serialization / reconstruction of QHTML object trees as well as provide an editable JSON array that can be modified without creating the actual objects and then instantiated as required by appending to the various arrays. This should be the core of the page-builder, meaning that everything that the page builder does should operate on this exact data structure that can be retrieved, modified and then set using .fromJSON() on any QHTMLNode C++ class. calling .fromJSON will recursively call .fromJSON for each object inside of qhtmlChildren arrays. ``` document.querySelector("q-html").fromJSON([{ qhtmlType: "QHTMLComponentDefinition", qhtmlName: "othercomp", qhtmlChildren:[{qhtmlType: "QHTMLSignal", qhtmlName: "signal1", qhtmlParameters: ["val"]}, {qhtmlType: "QHTMLFunction", qhtmlName: "method1", qhtmlParameters: ["val"], qhtmlChildren: [ {qhtmlType: "QHTMLJavaScriptBlock", qhtmlContents: "alert(val);"}]}, {qhtmltype: "QHTMLProperty", qhtmlName: "value1", qhtmlValue: {type: "number", value: "42"}}]}, {qhtmlType: "QHTMLComponentDefinition", qhtmlName: "mycomp", qhtmlInherits:[ ], qhtmlChildren: [ , {qhtmlType: "QHTMLAnonNode", ..., {qhtmlType: "QHTMLEventHandler", ...}}] }]) ``` This willl create a new QHTMLComponentDefinition in the QHTMLDomTree for this element named othercomp, and then call .fromJSON with the object for `othercomp` which will populate the properties, signals, functions, slots, etc, and then for each object in qhtmlChildren, create a new object based on the qhtmlType and then call .fromJSON() on that newly created object. This willl ease the burdon of having to constantly convert and parse raw QHTML source by allowing for the page-builder (and QHTML as a whole) to operate on JSON data for manipulation and data flow between non-qhtml areas and qhmtl areas of the code base. The last thing is going to be .reload() on QHTMLDomTree(). This reload() method will essentially take the QHTMLDomTree and call .toQHTML() and store it in a variable. Next it will create a