diff --git a/plugins/simulator/docs/user-flows/cdu-dom-tree-reference.md b/plugins/simulator/docs/user-flows/cdu-dom-tree-reference.md index 86eda7b..34f3f90 100644 --- a/plugins/simulator/docs/user-flows/cdu-dom-tree-reference.md +++ b/plugins/simulator/docs/user-flows/cdu-dom-tree-reference.md @@ -446,6 +446,9 @@ Nesting depth → `data-depth` + the `--depth` CSS variable. Active path → `.a ``` > Render path is chosen by mime `value.type`: images → `.file__item__ img`; PDFs/docs → > `.pg-viewer`/`.pdf-viewer`. Needs a complete File `value` (see §9). +> **`align` works from config on a *standalone* `file`/`image`** — `align:"center"|"right"` puts a +> `.center__`/`.right__` modifier on the root (default `.left__`), no CSS needed. A **`file` cell inside +> a table has no `align`** field, so it stays `left__` and can only be re-aligned via CSS. ### upload (default / webcam) ``` @@ -532,6 +535,19 @@ Common wrapper (all `type`): - Group title row: `
→ group.title` - Then the usual `` rows of that group. +- **Numbered pagination** (`extra.page`/`extra.totalPages`) renders as a **separate block, sibling of + `[class*="table__wrap"]`** inside the table root (not part of ``), and defaults to + `position:absolute`: + ``` + # .table.table__group … + ├ [class*="table__wrap"] >
… ← the rows + └ [class*="table__pagination"] ← the pager (sibling, position:absolute) + ├ [class*="table__pagination__button"] #-first / -prev / -next / -last (icon-only, g-secondary g-small) + └ [class*="table__pagination__current"] → current page number + ``` + The `-next`/`-last` buttons reuse the `-prev`/`-first` arrow with `[class*="rotate__"]`. Because the + pager is `position:absolute` and `[class*="table__wrap"]` carries a default `max-height`, laying the + pager under a re-flowed table needs a CSS re-place (recipe in the `simulator-styles` skill). ### type=default — cell mini-components Each cell renders inside ``; the cell type @@ -561,6 +577,11 @@ decides its contents: → 33% / 67%). The reliable group hook is `.row__` (flex container); items via `[class*="row__item"]` (note: the item wrapper has **only** the hashed `.row__item__…` class — there is no stable bare `.row__item` class, so use the substring selector). +- **Multi-token `row`** — the `row` value is space-separated: the first token is the row id (→ + `.row__`), and every **extra token is emitted as a literal class** on the row wrapper. So + `row:"1 my_row"` → `
…`; `.my_row` is a stable, author-chosen + hook (usable across rows). This is the only way to put your own class on a row — a `styleClass` on a + `row` is dropped. ### sortable section + contentLoop ``` diff --git a/plugins/simulator/docs/user-flows/cdu-page-protocol.md b/plugins/simulator/docs/user-flows/cdu-page-protocol.md index 4024e08..e69d7bc 100644 --- a/plugins/simulator/docs/user-flows/cdu-page-protocol.md +++ b/plugins/simulator/docs/user-flows/cdu-page-protocol.md @@ -299,6 +299,12 @@ All component `class` values (renderer `ComponentClasses`). Most are content/inp > swagger ("Simulator.Company Scripts", `components.schemas`). Treat that spec as the source of > truth for individual component options. +> **`mainMenu` inline-vs-popover depth** — `mainMenu.extra.maxInlineDepth` (default **1**) sets how +> deep branches expand **inline** (accordion, via native `
`); levels below it open as a +> **hover popover** instead, so raise it for a full inline accordion. `extra.submitOnBranchToggle` +> (default `false`) toggles a branch client-side with no `/send`; `true` posts `action:expand|collapse` +> on each toggle. `extra.autoExpandActive` auto-opens the ancestors of the active node. + --- ## 6. Templating & data binding @@ -502,6 +508,11 @@ applies no author class. A `styleClass` on a normal component (`upload`, `label` `select`, `edit`, …) **is** emitted onto that element. Style the leaf elements, never the `row` wrapper. +> **Workaround — give a row its own class:** the `row` value is space-separated. The first token is +> the row id (→ `.row__`), and every **extra token the renderer emits as a literal class** on the +> generated row wrapper. So `row:"1 my_row"` puts `.my_row` on the wrapper — the one stable hook you +> can style a whole row by (and reuse across rows: `row:"1 my_row"` + `row:"2 my_row"` share `.my_row`). + ### 12.3 No client-side conditional visibility — reveal = `submitOnChange` + 200 `changes` `visibility` is a static enum (`visible|disabled|hidden`); there is no expression/binding language, so a field cannot show/hide reactively from another field's value on the client. The only way to @@ -551,6 +562,16 @@ So an `edit` with `visibility:"visible"` but a `styleClass` that hides it in CSS idiomatic carrier for a value set by `changes[]` (e.g. the selection behind button-cards). A field with `visibility:"hidden"` is NOT submitted. +### 12.7 `table type:"group"` hides ungrouped rows; its pager sits outside the rows +Numbered table pagination requires **`type:"group"`** (a `default` table has no page controls) with +`extra.page` / `extra.totalPages`. Three traps: **(1)** `type:"group"` **hides every row without a +`groupValue`** — switching a `default` table to `group` without also giving each `body` row a +`groupValue` (and declaring ≥1 group in `extra.groups`) leaves an empty table with only the pager. +**(2)** An empty group still renders a group-title header row (`[class*="table__row__group"]`) — hide +it when the group is a technical, title-less one. **(3)** The pager renders as a **separate block, not +inside the rows**, so it needs CSS to sit under the table (DOM hooks in `cdu-dom-tree-reference.md` §7; +positioning recipe in the `simulator-styles` skill). + --- ## Related documentation diff --git a/plugins/simulator/skills/simulator-styles/SKILL.md b/plugins/simulator/skills/simulator-styles/SKILL.md index adf2a8b..a7d84c0 100644 --- a/plugins/simulator/skills/simulator-styles/SKILL.md +++ b/plugins/simulator/skills/simulator-styles/SKILL.md @@ -410,17 +410,44 @@ input, textarea, .button > span, .toggle__title, `[data-class="grid-one-column"] { max-width:440px; margin:20px auto; padding:20px; background:#fff; border-radius:8px; box-shadow:0 0 10px #0000001a; }` - **Full-bleed admin** — unlock the cap: `.content__main [data-class="grid-one-column"] { max-width:none; }` - **Fixed sidebar shell** — `.sidebar { width:250px; grid-template-rows:80px 1fr 120px; }` with header/content/footer panes. +- **Sidebar `styleClass` + `mainMenu` indent trap** — the sidebar rail **ignores `grid.sideBar.styleClass`**; style it via the base slots `.sidebar` / `.sidebar__header|__content|__footer` / `.page__sidebar` (layout — fixed width, pinned footer — as in *Fixed sidebar shell* above). For a `mainMenu` inside it, **do not** target `[class*="mainMenuItem"]` — the substring also matches the branch wrappers (`.mainMenuItemGroup`, `__summary`, `__dropdown`), so `padding` accumulates per level into huge gaps. Zero the wrappers, style only the exact `.mainMenuItem` (the clickable row), and indent by level with the renderer's `--depth` var: + ```less + nav.mainMenu [class*="mainMenuItemGroup"], nav.mainMenu details, nav.mainMenu summary { margin:0 !important; padding:0 !important; } + nav.mainMenu .mainMenuItem { padding:7px 10px; padding-left:calc(10px + var(--depth,0) * 14px); } + ``` - **Pinned footer + scrolling body** — `.info_form{height:calc(100vh - 50px); display:flex; flex-direction:column;} .content_section{flex:1; max-height:calc(100vh - 100px);}` +- **Card-per-iteration wrapper** — a bare `contentLoop` renders its iterations flat, with no per-iteration box. Set the section's `sortable:true` and the renderer wraps **each iteration** in a `.draggable` card with a drag handle; hide the handle to drop the drag affordance and keep just the wrapper (hiding `.draggable__handle` fully disables dragging — it is the only activator — so there is no `reorder` submit to handle): + ```less + .section__content .draggable__handle { display:none !important; } /* kill grip → drag off */ + .section__content .draggable { border:none !important; background:transparent !important; + padding:0 !important; box-shadow:none !important; cursor:default; } /* reset outer card, avoid double frame */ + .section__content .draggable__content { border:1px solid #ececec; border-radius:10px; padding:14px; } + ``` +- **Give a `row` its own class (multi-token `row`)** — `styleClass` on a `row` is dropped, but the `row` value is space-separated: the first token is the row id (→ `.row__`), and every **extra token the renderer adds as a literal class** on the row wrapper. So `row:"1 my_row"` and `row:"2 my_row"` both get `.my_row` — a shared, stable hook to style many rows at once (the hashed `.row__…` is per-build and unreliable). ### Components — re-skin, don't accept defaults - **Floating-label input** — `.edit__text .label { top:36px; } .edit__text.selected .label { top:16px; font-size:12px; } .edit__text.focus .field { border-color:@blue; box-shadow:0 0 0 2px #007bff40; }` - **Custom checkbox** — hide native (`input{-webkit-appearance:none}`, `i{display:none}`), draw tick on `.checked input::after` (rotated border). - **Card data table** — `.main_table { border-radius:12px; border:1px solid @border; } .main_table [class*="table__wrap"]{ overflow:hidden; border-radius:12px 12px 0 0; }` + custom pagination by swapping `[id*="table-next"] i::after { content:""; background-image:@icon_arrow_right; }`. Use **modifier classes**: `.main_table.thin_table`, `.no_thead`, `.center_cell`. - **Table/radio → cards** (the e-commerce look): `thead{display:none}`, `tbody{display:flex;flex-direction:column;gap:10px}`, `.table-row{border:1px solid #ddd;border-radius:8px;padding:10px}`, highlight selection with `.table-row:has([class*="radioItem"].checked){border-color:#34303d}`. +- **Table pagination at the bottom, centered** — a `table type:"group"`'s pagination is a **sibling** of `[class*="table__wrap"]` (not part of the rows) and defaults to `position:absolute` (pinned top, overlapping content). Lay the table root as a column and re-place it: + ```less + .myTable { display:flex; flex-direction:column; } + .myTable [class*="table__wrap"] { max-height:none !important; height:auto !important; } + .myTable [class*="table__pagination"] { position:static !important; order:99; + display:flex; justify-content:center; gap:8px; margin-top:20px; } + ``` - **Status chip** — state-class + token map: `.status_cell.completed-… div{ background:@color_completed; }` (backend sets `styleClass:"status_cell completed-…"`). - **Button variants** — base `.button` + semantic modifiers (`.blue_btn`, `.red_button`, `.submit_btn`); hover-lift (`transform:scale(1.02); box-shadow:…`) or an animated `::after` fill-sweep (`transform:skewX(-20deg)`). - **Toast notifications** — centered, severity icon via `[class*="notifyItem"][class*="success"]::before{ background-image:@icon_success; }`. - **Modal sizing** — base `.modal` + `:has(.)` to size each, or size-class match `&[class*="xlarge"]{ width:80% !important; }`. +- **Breadcrumbs (via `tab`)** — model a crumb trail as a `tab` (`options[]` = crumbs, each `{value,title}`; `value` = current; `submitOnChange:true` so a click posts the value). Two `tab` traps surface only once you restyle it: the label `[class*="i-label"]` defaults to `width:0; overflow:hidden` (text clips to nothing), and in a fixed tab (`i-fixed`) each `[class*="tab__item"]` collapses to `width:0` (options overlap). Fix both with `width:auto`: + ```less + .breadcrumbs [class*="tab__item"] { width:auto !important; min-width:max-content !important; + flex:0 0 auto !important; margin:0 !important; } + .breadcrumbs [class*="tab__item"] [class*="i-label"] { width:auto !important; overflow:visible !important; } + .breadcrumbs [class*="tab__item"]:not(:last-child)::after { content:"›"; margin:0 10px; opacity:.6; } + ``` ### Interactivity (CSS-only, no backend round-trip) - **Accordion / dropdown / collapsible tree** — a `check`/`toggle` with `.checked`, expanded by parent `:has()`, animated via `grid-template-rows:0fr → 1fr`: @@ -428,6 +455,7 @@ input, textarea, .button > span, .toggle__title, .menu { display:grid; grid-template-rows:0fr; overflow:hidden; transition:grid-template-rows .32s; } .sidebar:has(.nav_btn.checked) .menu { grid-template-rows:1fr; } ``` + When the toggle and its target are **siblings in the same `.section__content`** (the common case), prefer the sibling combinator — no `:has()` needed: `.myCheck.checked ~ .myCollapsible { max-height:300px; }`. `.checked` lands on the component's outer wrapper. Reserve `:has()` for non-sibling targets. - **Slide-in panel / popover** — toggle `opacity`/`transform`/`pointer-events` under `&:has(.trigger.checked)`. ### State without CSS-only tricks @@ -484,10 +512,22 @@ Advanced Less available: `each()`, guards (`when`), maps + indirect lookup (`@@k - **Winning the cascade**: component CSS-modules load after you and win equal-specificity ties — beat them with a chain of stable classes (`styleClass` + `[class*="…"]` + tag), not `#id` or a lone `!important`. Full worked example + cascade math in §"How Smart Form styling works" pt 5. +- **Never write `.cdu-page` yourself**: the compiler wraps your whole stylesheet in `.cdu-page { … }` + at serve-time, so a hand-written `.cdu-page` (or `.cdu-page:has(…)`) becomes a *descendant* + (`.cdu-page .cdu-page…`) and silently never matches. The page root is `&` (= `.cdu-page`): paint the + page with `& { background:#e30613; min-height:100vh; }` and target it with `&:has(…)`. - **Image/`file` cell renders "wide but a thin strip"**: not a width bug but a **clipping ancestor** — a wrapper (`.file` / `.file__item`) keeps a small fixed height + `overflow:hidden`. Force the box at **every** wrapper level (`height` + `min/max-height` + `width:100%` + `max-width:none` + - `overflow`), not only on ``. + `overflow`), not only on ``. The default clamp is `max-height:32px` on both `` and + `[class*="table__img"]`, and `max-height` always beats `height` — so `max-height:none` is the key. + One height knob down the whole chain (`.card-img` = the cell's `styleClass`, applied on `
`): + ```less + .card-img, .card-img [class*="table__img"], .card-img .file, .card-img [class*="file__item"] { + width:100%; height:240px !important; max-height:none !important; display:block; } + .card-img img { width:100% !important; height:100% !important; max-width:none !important; + max-height:none !important; object-fit:contain; } /* contain = whole image; cover = fill + crop */ + ``` - **Dead `styleClass`**: placeholder classes (`my-custom-class`) with no rule are harmless but noise — remove them. - **Stale copies**: forms accumulate `style(copy)` / `style BACKUP` files — they're not wired in;