component mechanics
This commit is contained in:
@@ -0,0 +1,259 @@
|
||||
# NiceGUI Component Mechanics
|
||||
|
||||
Use this reference to understand how customization crosses the NiceGUI Python wrapper, Quasar component, Vue runtime, and browser DOM. It owns constructor behavior, prop translation, events, bindings, slots, frontend methods, detached content, and component-specific caveats. For themes, utility classes, CSS properties, responsive page composition, and other cosmetic work, load [visual styling and CSS](./styling-and-customization.md).
|
||||
|
||||
## Framework Boundary Model
|
||||
|
||||
A NiceGUI component is not a Python-rendered HTML fragment. Customization passes through several owners:
|
||||
|
||||
| Layer | Owns | Inspect when |
|
||||
| --- | --- | --- |
|
||||
| NiceGUI Python wrapper | constructor arguments, Python value normalization, validation, bindings, event callbacks, and update helpers | behavior may already have a typed Python API or wrapper-specific state rules |
|
||||
| NiceGUI element bridge | serialized props, classes, styles, events, slots, and frontend method calls | mapping a supported Vue or Quasar feature through NiceGUI |
|
||||
| Quasar Vue component | documented props, emitted events, named slots, public methods, popup behavior, accessibility, and internal state | the NiceGUI constructor does not expose a required component feature |
|
||||
| Vue and browser runtime | reactivity, rendered DOM, teleported content, CSS cascade, fonts, and static assets | diagnosing placement, asset loading, or content rendered outside the element subtree |
|
||||
|
||||
Treat the generated DOM beneath a Quasar component as private implementation detail. Work through the highest owning layer that expresses the requirement.
|
||||
|
||||
## How The APIs Map
|
||||
|
||||
Use this map after confirming the exact API against the installed NiceGUI and bundled Quasar versions:
|
||||
|
||||
| Requirement | NiceGUI surface | Underlying mechanic |
|
||||
| --- | --- | --- |
|
||||
| Wrapper-supported value or behavior | constructor argument, binding, or helper such as `set_options()` | Python normalizes state and synchronizes the component |
|
||||
| Additional Quasar option | `.props(...)` | values become props on the wrapped Vue component |
|
||||
| Browser or Quasar notification | constructor callback or `.on(...)` | an emitted frontend event is forwarded to a Python handler |
|
||||
| Semantic insertion point | `add_slot(...)` or a wrapper-specific slot API | content renders in a named Vue slot |
|
||||
| Imperative frontend action | a NiceGUI helper or `run_method(...)` | NiceGUI invokes a public method on the client component |
|
||||
| Page placement or appearance | `.classes(...)`, `.style(...)`, or an application stylesheet | CSS applies to the rendered element; detached content needs its own class hook |
|
||||
|
||||
Do not copy a Vue template into Python. Translate each part according to its owner: constructor data stays in Python, Quasar props go through `.props()`, emitted events go through callbacks or `.on()`, and named Vue slots go through NiceGUI's slot API.
|
||||
|
||||
## State And Event Flow
|
||||
|
||||
Server-driven changes and user-driven changes cross a client-server boundary:
|
||||
|
||||
1. Python creates the wrapper and serializes initial state to the client.
|
||||
2. Vue renders the Quasar component from those props and slots.
|
||||
3. A browser interaction causes Quasar to update client state or emit an event.
|
||||
4. NiceGUI forwards registered events to Python handlers.
|
||||
5. Python mutations return through bindings, wrapper helpers, or an explicit `update()`.
|
||||
|
||||
Use wrapper helpers and bindings when available because they preserve NiceGUI's value model. Directly changing a Python collection or constructing a raw JavaScript object does not imply that the client receives the change.
|
||||
|
||||
## Detached Content And Assets
|
||||
|
||||
Some Quasar components render menus, dialogs, tooltips, and similar content outside the field or trigger's DOM subtree. A descendant CSS selector beneath the Python-created element will not reach that content. Use the component's documented popup or content class prop, then style that application-owned class separately.
|
||||
|
||||
Icons and other externally defined visuals add another boundary: a valid Quasar icon name identifies an asset but does not load its font or stylesheet. Confirm both the naming convention and the application-level asset registration.
|
||||
|
||||
## Component Customization Workflow
|
||||
|
||||
Research the target component before generating code or CSS. Do not rely on a remembered NiceGUI or Quasar API, and do not mix source versions.
|
||||
|
||||
### Establish The Version Pair
|
||||
|
||||
1. Read the target project's lockfile or installed package metadata to identify its exact NiceGUI version.
|
||||
2. Open `package.json` at that NiceGUI tag and read the exact `quasar` dependency version.
|
||||
3. Use the NiceGUI tag for both NiceGUI sources and the matching `quasar-v<version>` tag for both Quasar sources.
|
||||
|
||||
The curated component sections below use NiceGUI `3.16.0` and Quasar `2.18.5`. The pairing comes from [NiceGUI `v3.16.0` frontend dependencies](https://github.com/zauberzeug/nicegui/blob/v3.16.0/package.json). Repeat the version check when the target application uses another NiceGUI release. Never infer compatibility from Quasar's latest release or use NiceGUI `main` with Quasar `dev`.
|
||||
|
||||
### Research Four Sources
|
||||
|
||||
Review these sources in order for the selected version pair:
|
||||
|
||||
1. **NiceGUI documentation:** identify the supported Python API and documented examples for the component.
|
||||
2. **NiceGUI source code:** inspect constructor normalization, validation, props, bindings, events, helpers, and the wrapped frontend component.
|
||||
3. **Quasar documentation:** identify the wrapped component's public props, slots, events, methods, accessibility behavior, and documented warnings.
|
||||
4. **Quasar source code:** verify how those public APIs behave, especially popup mounting, model translation, event flow, rendering, and public methods.
|
||||
|
||||
Use current upstream sources only when the target version is unavailable, and state that fallback explicitly. If the installed package differs from its tag, follow the installed implementation and record the difference.
|
||||
|
||||
### Apply The Findings
|
||||
|
||||
For every component section:
|
||||
|
||||
1. Link the four version-matched sources under **Research Sources**.
|
||||
2. Summarize which layer owns the behavior under **Ownership Result**.
|
||||
3. Order the supported customization surfaces from highest-level NiceGUI API to lower-level Quasar or CSS mechanisms.
|
||||
4. Include an example only after the owning APIs are established.
|
||||
5. Curate a short caveat list from the four sources. Keep only constraints that change implementation, security, accessibility, performance, or testing decisions.
|
||||
|
||||
If the requirement is purely visual after this ownership check, continue in [visual styling and CSS](./styling-and-customization.md).
|
||||
|
||||
## Using Slots In NiceGUI
|
||||
|
||||
A NiceGUI element is the Python-side representation of a browser component. Many elements wrap Quasar Vue components, whose insertion points are exposed as slots. A simple container normally uses one default slot; more complex components expose named slots such as `prepend`, `append`, `option`, `header`, or `body-cell-*`. The available names and their contracts belong to the wrapped component, so verify them in the version-matched Quasar documentation.
|
||||
|
||||
NiceGUI creates a default slot for every element. Entering an element as a context manager enters that default slot, and entering `element.add_slot(name)` selects a named slot. NiceGUI keeps the active slots on a task-local stack; each element constructed inside the `with` block becomes a child of the innermost active slot.
|
||||
|
||||
These mechanics are defined by the tagged [`Element.add_slot()` implementation](https://github.com/zauberzeug/nicegui/blob/v3.16.0/nicegui/element.py), the [`Slot` context manager](https://github.com/zauberzeug/nicegui/blob/v3.16.0/nicegui/slot.py), and NiceGUI's [context-managed scoped-slot examples](https://github.com/zauberzeug/nicegui/blob/v3.16.0/website/documentation/content/table_documentation.py).
|
||||
|
||||
### Prefer Context-Managed NiceGUI Elements
|
||||
|
||||
Build slot content with ordinary NiceGUI elements by default:
|
||||
|
||||
```python
|
||||
name_input = ui.input("Name")
|
||||
|
||||
with name_input.add_slot("prepend"):
|
||||
ui.icon("person")
|
||||
```
|
||||
|
||||
Use nested context managers to express the component hierarchy. This preserves NiceGUI element identity, event registration, updates, deletion, and test visibility. Pass a raw Vue template to `add_slot(name, template)` only when the slot requires client-side structure that ordinary NiceGUI elements cannot express cleanly, such as a `v-for` that creates a variable number of sibling elements.
|
||||
|
||||
### Use Scoped Props On The Client
|
||||
|
||||
A scoped slot receives a `props` object from its owning Vue component. Since NiceGUI `3.5.0`, NiceGUI elements inside a scoped-slot context can reference that object in dynamic `.props()` expressions and JavaScript event handlers:
|
||||
|
||||
- use `.props(":label=props.value")` or another component-supported prop to display a scoped value
|
||||
- use `.props("v-bind=props.itemProps")` when the slot provides a bundle of required attributes and handlers
|
||||
- use `.on(..., js_handler="... emit(...)", handler=...)` to transform and send serializable scoped values to Python
|
||||
|
||||
Scoped props exist only in the browser render context. They are not Python variables and cannot be read by a Python callback until a JavaScript handler emits the required values. Treat `innerHTML`, `v-html`, and raw template interpolation as untrusted HTML unless the source is explicitly sanitized.
|
||||
|
||||
### Preserve The Slot Contract
|
||||
|
||||
Replacing default slot content also replaces the wrapped component's default rendering. Preserve any documented slot-prop bundle that carries behavior. For example, a `QSelect` option slot must bind `props.itemProps` to its root item; otherwise the custom row can lose click selection, disabled state, focus, active state, and keyboard navigation. Keep one root element per virtual-scroll item unless the component documents how to mark additional siblings.
|
||||
|
||||
## `ui.select`
|
||||
|
||||
### Research Sources
|
||||
|
||||
- **NiceGUI documentation:** [`ui.select` documentation source at `v3.16.0`](https://github.com/zauberzeug/nicegui/blob/v3.16.0/website/documentation/content/select_documentation.py)
|
||||
- **NiceGUI source code:** [`Select` implementation at `v3.16.0`](https://github.com/zauberzeug/nicegui/blob/v3.16.0/nicegui/elements/select.py)
|
||||
- **Quasar documentation:** [`QSelect` documentation source at `2.18.5`](https://github.com/quasarframework/quasar/blob/quasar-v2.18.5/docs/src/pages/vue-components/select.md)
|
||||
- **Quasar source code:** [`QSelect` implementation at `2.18.5`](https://github.com/quasarframework/quasar/blob/quasar-v2.18.5/ui/src/components/select/QSelect.js)
|
||||
|
||||
### Ownership Result
|
||||
|
||||
NiceGUI's `Select` wraps Quasar `QSelect` but owns important Python-side behavior. Its constructor handles options, labels, values, change callbacks, input filtering, new-value modes, multiple selection, clearing, validation, and key generation. Use those constructor parameters before adding equivalent Quasar props manually.
|
||||
|
||||
### Customization Order
|
||||
|
||||
1. Use `options`, `label`, `value`, `on_change`, `with_input`, `new_value_mode`, `multiple`, `clearable`, `validation`, and `key_generator` through the NiceGUI constructor.
|
||||
2. Use `.props()` for additional documented `QSelect` behavior such as field design, chips, option density, popup classes, popup positioning, or menu/dialog behavior.
|
||||
3. Use `.classes()` and Tailwind for the field's structural width and placement.
|
||||
4. Use named slots for prepend, append, loading, no-option, selected, or option content when props are insufficient.
|
||||
5. Preserve the documented scoped-slot props when replacing option content so Quasar retains selection and keyboard behavior.
|
||||
|
||||
### Example: Custom Menu Options With A Scoped Slot
|
||||
|
||||
`QSelect` supplies each option as `props.opt` and its interaction contract as `props.itemProps`. NiceGUI elements can consume both inside the slot context without a raw Vue template:
|
||||
|
||||
```python
|
||||
from nicegui import ui
|
||||
|
||||
item_select = ui.select(
|
||||
options={"chair": "Chair", "desk": "Desk", "lamp": "Lamp"},
|
||||
label="Item",
|
||||
value="chair",
|
||||
clearable=True,
|
||||
with_input=True,
|
||||
).props("outlined options-dense")
|
||||
|
||||
with item_select.add_slot("prepend"):
|
||||
ui.icon("search")
|
||||
|
||||
with item_select.add_slot("option"):
|
||||
with ui.item().props("v-bind=props.itemProps"):
|
||||
with ui.item_section().props("avatar"):
|
||||
ui.icon("inventory_2")
|
||||
with ui.item_section():
|
||||
ui.badge().props(":label=props.opt.label outline color=primary")
|
||||
```
|
||||
|
||||
The `prepend` slot adds content around the field. The scoped `option` slot replaces every menu row with context-managed NiceGUI elements; the badge reads the browser-side option label through a dynamic Quasar prop. Keep `v-bind=props.itemProps` on the root `ui.item()` so the custom rendering retains the option's interaction and accessibility wiring.
|
||||
|
||||
### Curated Caveats
|
||||
|
||||
These caveats are distilled from the four version-matched sources above:
|
||||
|
||||
- NiceGUI accepts a list of values or a dictionary mapping values to labels. Do not assume the Python options model is the same as Quasar's JavaScript object-array examples.
|
||||
- After mutating `options`, call `update()` or use `set_options()` so the client receives the change.
|
||||
- `new_value_mode` enables input automatically. For dictionary options with `add`, NiceGUI requires a `key_generator`.
|
||||
- A multiple select has a list value. NiceGUI normalizes a non-list initial value, but application state should still use the intended list shape.
|
||||
- `map-options` has a Quasar performance cost. Do not add it to NiceGUI's mapped options without confirming that the wrapper's value translation requires it.
|
||||
- `display-value-html` and `options-html` can create cross-site scripting risk. When using `selected`, `selected-item`, or `option` slots, the application owns sanitization.
|
||||
- A custom `option` slot must bind `props.itemProps` to its root `ui.item()` so click, focus, active, disabled, and keyboard behavior remain connected.
|
||||
- Custom option slots use virtual scrolling. When one option renders multiple sibling elements, Quasar requires `q-virtual-scroll--with-prev` on every additional sibling.
|
||||
- Buttons placed in `before`, `after`, `prepend`, or `append` field slots do not propagate clicks to the parent. A submit button in one of those slots needs its own submit handler.
|
||||
- `QSelect` renders its popup outside the field. Style it through `popup-content-class`; do not assume a descendant selector beneath the field will reach it.
|
||||
- Quasar switches between menu and dialog popup behavior by platform. Verify forced `behavior=menu` carefully on iOS when input filtering is enabled.
|
||||
|
||||
Use `.on()` or `run_method()` only after confirming the event or method in the installed Quasar API. Prefer NiceGUI's `on_change`, `set_options()`, value bindings, and `is_showing_popup` when they cover the behavior.
|
||||
|
||||
## `ui.icon`
|
||||
|
||||
### Research Sources
|
||||
|
||||
- **NiceGUI documentation:** [`ui.icon` documentation source at `v3.16.0`](https://github.com/zauberzeug/nicegui/blob/v3.16.0/website/documentation/content/icon_documentation.py)
|
||||
- **NiceGUI source code:** [`Icon` implementation at `v3.16.0`](https://github.com/zauberzeug/nicegui/blob/v3.16.0/nicegui/elements/icon.py)
|
||||
- **Quasar documentation:** [`QIcon` documentation source at `2.18.5`](https://github.com/quasarframework/quasar/blob/quasar-v2.18.5/docs/src/pages/vue-components/icon.md)
|
||||
- **Quasar source code:** [`QIcon` implementation at `2.18.5`](https://github.com/quasarframework/quasar/blob/quasar-v2.18.5/ui/src/components/icon/QIcon.js)
|
||||
|
||||
### Ownership Result
|
||||
|
||||
NiceGUI's `Icon` is a thin `QIcon` wrapper. Its constructor exposes `name`, `size`, and `color`; the source forwards these to a `q-icon` element. Use Quasar's icon naming and asset rules for anything beyond those parameters.
|
||||
|
||||
### Customization Order
|
||||
|
||||
1. Choose an icon family that is actually loaded by the application.
|
||||
2. Pass the documented icon name, size, and color to `ui.icon()`.
|
||||
3. Use `.props()` for supported `QIcon` props such as `left`, `right`, or a custom render tag.
|
||||
4. Use `.classes()` for structural placement and an application class for stable visual variants.
|
||||
5. Use a static stylesheet for Material Symbol axes, state variants, custom webfonts, or repeated effects.
|
||||
|
||||
### Example
|
||||
|
||||
```python
|
||||
from nicegui import ui
|
||||
|
||||
ui.icon(
|
||||
"sym_o_home",
|
||||
size="1.5rem",
|
||||
color="primary",
|
||||
).classes(
|
||||
"app-symbol-filled shrink-0"
|
||||
).tooltip(
|
||||
"Home"
|
||||
)
|
||||
```
|
||||
|
||||
```css
|
||||
.app-symbol-filled {
|
||||
font-variation-settings:
|
||||
"FILL" 1,
|
||||
"wght" 400,
|
||||
"GRAD" 0,
|
||||
"opsz" 24;
|
||||
}
|
||||
```
|
||||
|
||||
### Curated Caveats
|
||||
|
||||
These caveats are distilled from the four version-matched sources above:
|
||||
|
||||
- Material icon names use snake case. Material variants use prefixes such as `o_`, `r_`, `s_`, `sym_o_`, `sym_r_`, and `sym_s_`.
|
||||
- Other icon families have their own prefixes and require their webfont or stylesheet to be loaded. A valid name does not load the corresponding asset.
|
||||
- `size` accepts CSS units or Quasar sizes such as `xs`, `sm`, `md`, `lg`, and `xl`. Quasar implements icon sizing through `font-size`.
|
||||
- Icon color inherits text color unless the `color` prop or a CSS color overrides it.
|
||||
- Material Symbol variable axes apply to webfont icons, not static SVG icon exports.
|
||||
- Quasar also supports SVG path strings, `svguse:` references, and `img:` URLs. Confirm the exact `QIcon` name format and mount path before generating one of these forms.
|
||||
- `QIcon` renders with `aria-hidden="true"`. For an action, use a semantic control such as `ui.button(icon=..., on_click=...)` and put the accessible name on that control; a tooltip is supplementary.
|
||||
- Prefer `ui.icon(...).tooltip(...)` over manually constructing tooltip slot markup when NiceGUI's method covers the visual hint.
|
||||
|
||||
## Completion Check
|
||||
|
||||
Before accepting a special-component customization:
|
||||
|
||||
1. Record the target NiceGUI version and its declared Quasar version.
|
||||
2. Link the version-matched NiceGUI documentation and source code.
|
||||
3. Link the version-matched Quasar documentation and source code.
|
||||
4. Identify constructor arguments, Quasar props, slots, Tailwind classes, and stylesheet rules separately.
|
||||
5. Confirm detached popup or external asset behavior where applicable.
|
||||
6. Keep the caveat list traceable to the four researched sources.
|
||||
7. Test keyboard interaction, focus, labels, and tooltips.
|
||||
8. Test the supported mobile, landscape desktop, and portrait desktop viewports.
|
||||
Reference in New Issue
Block a user