@thatopen/services 0.0.1 → 0.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (59) hide show
  1. package/AGENTS.md +76 -0
  2. package/CONTEXT.md +4 -4
  3. package/README.md +4 -4
  4. package/dist/cli.js +7 -5
  5. package/dist/core/client.d.ts +1 -1
  6. package/docs/access-backend-data.md +19 -0
  7. package/docs/app-architecture.md +94 -0
  8. package/docs/app-layout.md +139 -0
  9. package/docs/app-wiring.md +123 -0
  10. package/docs/bim-components/coding-conventions.md +128 -0
  11. package/docs/bim-components/element-collections.md +69 -0
  12. package/docs/bim-components/expose-events.md +84 -0
  13. package/docs/bim-components/library-examples.md +28 -0
  14. package/docs/bim-components/observable-collections.md +83 -0
  15. package/docs/bim-components/overview.md +160 -0
  16. package/docs/bim-components/per-frame-updates.md +20 -0
  17. package/docs/bim-components/save-and-restore-state.md +21 -0
  18. package/docs/bim-components/setup-and-cleanup.md +64 -0
  19. package/docs/bim-components/type-conventions.md +49 -0
  20. package/docs/bim-components/user-driven-object-creation.md +56 -0
  21. package/docs/cli-setup.md +54 -0
  22. package/docs/cloud-components.md +74 -0
  23. package/docs/connect-logic-to-ui.md +113 -0
  24. package/docs/previewing.md +45 -0
  25. package/docs/publishing.md +35 -0
  26. package/docs/scaffolding.md +54 -0
  27. package/docs/ui-components/async-actions.md +18 -0
  28. package/docs/ui-components/confirmation-dialog.md +39 -0
  29. package/docs/ui-components/data-table.md +185 -0
  30. package/docs/ui-components/display-text.md +39 -0
  31. package/docs/ui-components/inline-form.md +44 -0
  32. package/docs/ui-components/library-examples.md +24 -0
  33. package/docs/ui-components/overview.md +194 -0
  34. package/docs/ui-components/rendering-patterns.md +129 -0
  35. package/docs/ui-components/sections-layout.md +123 -0
  36. package/docs/update-grid-elements.md +46 -0
  37. package/docs/using-colors.md +32 -0
  38. package/docs/using-icons.md +37 -0
  39. package/package.json +3 -1
  40. package/src/cli/templates/bim/CONTEXT.md +3 -3
  41. package/src/cli/templates/bim/package.json +1 -1
  42. package/src/cli/templates/bim/src/app.ts +1 -1
  43. package/src/cli/templates/bim/src/bim-components/CloudRunner/index.ts +1 -1
  44. package/src/cli/templates/bim/src/main.ts +1 -1
  45. package/src/cli/templates/bim/src/setups/ui-manager.ts +1 -1
  46. package/src/cli/templates/bim/src/setups/viewports-manager.ts +1 -1
  47. package/src/cli/templates/cloud/CONTEXT.md +4 -4
  48. package/src/cli/templates/cloud/package.json +1 -1
  49. package/src/cli/templates/cloud/src/main.ts +1 -1
  50. package/src/cli/templates/cloud-test/package.json +1 -1
  51. package/src/cli/templates/cloud-test/src/main.ts +1 -1
  52. package/src/cli/templates/default/CONTEXT.md +4 -4
  53. package/src/cli/templates/default/package.json +3 -0
  54. package/src/cli/templates/default/src/main.ts +3 -3
  55. package/src/cli/templates/shared/AGENTS.md +23 -0
  56. package/src/cli/templates/shared/CLAUDE.md +1 -0
  57. package/src/cli/templates/shared/cloud/vite.config.js +3 -3
  58. package/src/cli/templates/test/package.json +1 -1
  59. package/src/cli/templates/test/src/main.ts +2 -2
@@ -0,0 +1,185 @@
1
+ # Data Table
2
+
3
+ > **Structural rule:** A `<bim-table>` always lives in its own dedicated UI component (e.g., `collisionsTable`, `elementsTable`). Never define a table inline inside another template such as a `panel-section`. The consuming template receives the table component as an external reference and composes it in.
4
+
5
+ `BUI.Table<TData>` is the standard component for displaying tabular data. `TData` is a record type where each key is a column name and each value is the cell's data type.
6
+
7
+ ## Data Type
8
+
9
+ Define a named type following the `{PascalCase(identifier)}Data` convention and export it from `src/types.ts`:
10
+
11
+ ```ts
12
+ export type MyComponentTableData = {
13
+ Name: string
14
+ Count: number
15
+ Actions: string // reserved for per-row interaction buttons
16
+ }
17
+ ```
18
+
19
+ `Actions: string` is the conventional column for per-row interaction buttons — typed as `string` (placeholder) and rendered via `dataTransform`.
20
+
21
+ Column names follow a casing rule: **PascalCase** for columns displayed to the user (`Name`, `Count`), **camelCase** for internal data columns not meant to be shown (`id`, `modelId`).
22
+
23
+ ## Template
24
+
25
+ The table element is created via `BUI.html` and configured imperatively once mounted using `BUI.ref`:
26
+
27
+ ```ts
28
+ export const myTableTemplate: BUI.StatefullComponent<MyTableState> = ({ components }) => {
29
+ const onCreated = (e?: Element) => {
30
+ if (!e) return
31
+ const table = e as BUI.Table<MyTableData>
32
+ table.loadFunction = async () => {
33
+ // return BUI.TableGroupData<MyTableData>[]
34
+ }
35
+ table.loadData(true)
36
+ }
37
+
38
+ return BUI.html`
39
+ <bim-table ${BUI.ref(onCreated)}></bim-table>
40
+ `
41
+ }
42
+ ```
43
+
44
+ **For async data** — assign `loadFunction` and call `loadData()`. Passing `true` forces a fresh fetch. `loadData()` does nothing if data already exists — to re-trigger, first reset: `table.data = []`.
45
+
46
+ **For synchronous data** — assign `table.data` directly:
47
+
48
+ ```ts
49
+ table.data = items.map(item => ({ data: { Name: item.name, Count: item.count } }))
50
+ ```
51
+
52
+ ### Data Structure
53
+
54
+ ```ts
55
+ {
56
+ data: { Name: "Juan", Count: 5 }, // required
57
+ children?: [...] // optional — nested rows
58
+ }
59
+ ```
60
+
61
+ The preferred pattern for hierarchical data is a **flat list combined with `groupedBy`**. Reserve `children` for fixed, explicit hierarchies.
62
+
63
+ ## Empty State
64
+
65
+ ```ts
66
+ return BUI.html`
67
+ <bim-table ${BUI.ref(onCreated)}>
68
+ <div slot="missing-data" style="display: flex; flex-direction: column; align-items: center;">
69
+ <bim-label>No data loaded.</bim-label>
70
+ <bim-button @click=${() => table.loadData()} label="Load Data"></bim-button>
71
+ </div>
72
+ </bim-table>
73
+ `
74
+ ```
75
+
76
+ ## Error State
77
+
78
+ ```ts
79
+ return BUI.html`
80
+ <bim-table ${BUI.ref(onCreated)}>
81
+ <div slot="error-loading" style="display: flex; flex-direction: column; align-items: center;">
82
+ <bim-label data-table-element="error-message"></bim-label>
83
+ <bim-button @click=${() => table.loadData()} label="Try Again"></bim-button>
84
+ </div>
85
+ </bim-table>
86
+ `
87
+ ```
88
+
89
+ ## Column Configuration and dataTransform
90
+
91
+ Column layout, visibility, and `dataTransform` are fixed properties — configure them in `onInstanceCreated`, not in the template.
92
+
93
+ ```ts
94
+ table.headersHidden = true
95
+ table.noIndentation = true
96
+ table.columns = [
97
+ "Name",
98
+ "Count",
99
+ { name: "Actions", width: "auto" },
100
+ ]
101
+ table.groupedBy = ["Type"]
102
+ table.preserveStructureOnFilter = true
103
+
104
+ table.dataTransform = {
105
+ Name: (value) => BUI.html`<bim-label>${value}</bim-label>`,
106
+ Actions: (_, rowData) => {
107
+ const { id } = rowData
108
+ if (!id) return _
109
+ return BUI.html`<bim-button @click=${() => doSomething(id)}></bim-button>`
110
+ }
111
+ }
112
+ ```
113
+
114
+ > `noIndentation` and `groupedBy` are incompatible.
115
+
116
+ **Column visibility:**
117
+ ```ts
118
+ table.visibleColumns = ["Name", "Actions"] // most columns hidden
119
+ table.hiddenColumns = ["id", "extension"] // most columns visible
120
+ ```
121
+
122
+ > Everything rendered inside a table lives in the Shadow DOM. CSS classes cannot be applied — all styling must use inline styles.
123
+
124
+ `rowData` can be mutated directly inside a transform:
125
+
126
+ ```ts
127
+ table.dataTransform = {
128
+ LoadBearing: (value, rowData) => {
129
+ const onChange = (e: Event) => {
130
+ const input = e.target
131
+ if (!(input instanceof BUI.Checkbox)) return
132
+ rowData.LoadBearing = input.checked
133
+ }
134
+ return BUI.html`<bim-checkbox @change=${onChange} .checked=${value}></bim-checkbox>`
135
+ }
136
+ }
137
+ ```
138
+
139
+ ## Grouping
140
+
141
+ ```ts
142
+ table.groupedBy = ["Discipline", "State"] // apply
143
+ table.groupedBy = [] // clear
144
+ ```
145
+
146
+ `groupingTransform` maps column values to arrays defining multi-level hierarchy:
147
+
148
+ ```ts
149
+ table.groupingTransform = {
150
+ State: (value) => {
151
+ if (value === "S0") return ["Work in Progress"]
152
+ if (value.includes(".")) return ["Shared States", parentState, value]
153
+ return ["Shared States", value]
154
+ }
155
+ }
156
+ ```
157
+
158
+ `BUI.Table.flattenData` flattens a nested group structure — useful for computing aggregate values:
159
+
160
+ ```ts
161
+ const children = BUI.Table.flattenData(group.data.children)
162
+ const total = children.reduce((sum, { data }) => sum + (Number(data.Count) || 0), 0)
163
+ ```
164
+
165
+ ## Row Events (rowcreated)
166
+
167
+ ```ts
168
+ const rowHandlers = new WeakMap<Element, { handleClick: () => void }>()
169
+
170
+ table.addEventListener("rowcreated", (e) => {
171
+ if (!("detail" in e)) return
172
+ const { row } = (e as { detail: BUI.RowCreatedEventDetail<MyTableData> }).detail
173
+
174
+ const existing = rowHandlers.get(row)
175
+ if (existing) row.removeEventListener("click", existing.handleClick)
176
+
177
+ const handleClick = () => {
178
+ if (!row.groupData?.data) return
179
+ const { Name } = row.groupData.data
180
+ }
181
+
182
+ rowHandlers.set(row, { handleClick })
183
+ row.addEventListener("click", handleClick)
184
+ })
185
+ ```
@@ -0,0 +1,39 @@
1
+ # Display Text
2
+
3
+ Always use `<bim-label>` for all text content inside templates.
4
+
5
+ ## Static text
6
+
7
+ ```ts
8
+ return BUI.html`<bim-label>Fixed text</bim-label>`
9
+ ```
10
+
11
+ ## Dynamic text (template interpolation)
12
+
13
+ ```ts
14
+ return BUI.html`<bim-label>${someText}</bim-label>`
15
+ ```
16
+
17
+ ## Updating text imperatively
18
+
19
+ Use `.textContent` to update a `bim-label` from outside the template. Never use `.label` — that property belongs to other components (`bim-button`, `bim-option`, etc.), not to `bim-label`:
20
+
21
+ ```ts
22
+ const label = section.querySelector("bim-label")!
23
+ label.textContent = "Updated text" // ✓ correct
24
+ label.label = "Updated text" // ✗ avoid — .label is not a property of bim-label
25
+ ```
26
+
27
+ ## The label attribute belongs to other components
28
+
29
+ `label` is an attribute/property of interactive components that display a text caption alongside an icon or control:
30
+
31
+ ```ts
32
+ // ✓ correct — label attribute on button, option, etc.
33
+ BUI.html`<bim-button label="Confirm"></bim-button>`
34
+ BUI.html`<bim-option label="Model A"></bim-option>`
35
+ BUI.html`<bim-text-input label="Name" vertical></bim-text-input>`
36
+
37
+ // ✗ avoid — label is not a property of bim-label
38
+ BUI.html`<bim-label label="Some text"></bim-label>`
39
+ ```
@@ -0,0 +1,44 @@
1
+ # Inline Form (Context Menu)
2
+
3
+ To let the user create or edit an entity without leaving the current view, place a `<bim-panel-section fixed>` acting as a form inside a `contextMenuTemplate`. The form opens as a popup anchored to the trigger button.
4
+
5
+ ```ts
6
+ const onBtnCreated = (e?: Element) => {
7
+ if (!e) return
8
+ const btn = e as BUI.Button
9
+ btn.contextMenuTemplate = () => {
10
+ const onSubmit = async ({ target }: { target: BUI.Button }) => {
11
+ const section = target.parentElement as BUI.PanelSection
12
+ section.valueTransform = {
13
+ name: (value: string) => value.trim(),
14
+ description: (value: string) => value.trim() || undefined,
15
+ }
16
+ const data = section.value as { name: string; description?: string }
17
+ target.loading = true
18
+ await doSomething(data)
19
+ target.loading = false
20
+ BUI.ContextMenu.removeMenus()
21
+ }
22
+
23
+ return BUI.html`
24
+ <bim-context-menu style="padding: 0; max-height: none;">
25
+ <bim-panel-section style="width: 16rem;" label="New Item" fixed>
26
+ <bim-text-input vertical name="name" label="Name"></bim-text-input>
27
+ <bim-text-input vertical name="description" type="area" label="Description"></bim-text-input>
28
+ <bim-button @click=${onSubmit} label="Create"></bim-button>
29
+ </bim-panel-section>
30
+ </bim-context-menu>
31
+ `
32
+ }
33
+ }
34
+
35
+ return BUI.html`<bim-button ${BUI.ref(onBtnCreated)} label="New Item"></bim-button>`
36
+ ```
37
+
38
+ ## Rules
39
+
40
+ - `contextMenuTemplate` must be assigned via `BUI.ref`, never inline in `BUI.html`.
41
+ - Inputs inside the form require the `name` attribute for `section.value` to pick them up.
42
+ - The `<bim-panel-section>` acting as a form always has `fixed` so it can't be collapsed.
43
+ - Use `section.valueTransform` to sanitize or coerce values before reading them.
44
+ - Call `BUI.ContextMenu.removeMenus()` after a successful submit to close the popup.
@@ -0,0 +1,24 @@
1
+ # Library Examples
2
+
3
+ Before writing any BUI component implementation, check whether an official example covers the pattern you need. Official examples are the best starting point: they show correct usage of BUI elements, proper rendering patterns, and idiomatic API usage.
4
+
5
+ ## How to find an example
6
+
7
+ 1. Fetch the `paths.json` below. Do not summarize the response — retain the full JSON in context and use the descriptions to reason about which examples are relevant.
8
+ 2. Use the descriptions in the JSON to reason about which examples best match the user's intent. Prefer semantic matching over path name matching — a description may cover what the user needs even if the component name doesn't match exactly.
9
+ 3. Only proceed to fetch an example if its description confirms that it directly covers the user's intent, or that it can serve as a building block for composing a new custom component. Do not fetch speculatively.
10
+ 4. Construct the full URL: `{base_url}{path_entry}` and fetch the example file to use as your implementation reference.
11
+
12
+ Note that some components have multiple examples for different use cases — `Table` for instance has separate entries for `Searching`, `Grouping`, `ExportingData`, and `DataTransform`. Check all relevant ones.
13
+
14
+ ---
15
+
16
+ ## Repository
17
+
18
+ ### engine_ui-components
19
+ BUI primitives from `@thatopen/ui` — the `packages/core/` entries.
20
+
21
+ - **paths.json**: `https://raw.githubusercontent.com/ThatOpen/engine_ui-components/refs/heads/main/examples/paths.json`
22
+ - **Base URL**: `https://raw.githubusercontent.com/ThatOpen/engine_ui-components/refs/heads/main/`
23
+
24
+ > The paths.json also contains entries under `packages/obc/` (OBC-connected tables and charts like `SpatialTree`, `ModelsList`, etc.). Those are also valid UI components — feel free to check them if the user needs that kind of structured data display.
@@ -0,0 +1,194 @@
1
+ # UI Component Creation
2
+
3
+ Guides and best practices for creating UI components with That Open Engine (BUI/OBC) — panels, tables, dropdowns, buttons, inputs, or any visual element.
4
+
5
+ ## Working mode
6
+
7
+ Before doing anything else, **read [`./library-examples.md`](./library-examples.md) and fetch the `paths.json` for `engine_ui-components` to understand what BUI components are already available.**
8
+
9
+ Only after that, **propose an implementation plan and wait for the user's approval.**
10
+
11
+ The proposal must describe:
12
+ - Which templates will be created or modified
13
+ - What state (`State`) each template will need
14
+ - Whether any template needs an `onInstanceCreated` callback
15
+ - Whether it composes other existing templates
16
+
17
+ Only proceed with changes after the user explicitly confirms the plan. If the scope is unclear, ask first — do not assume.
18
+
19
+ ---
20
+
21
+ ## Creating a UI Component
22
+
23
+ ### Step 1 — Define the component name
24
+
25
+ The component name is the source of truth. Everything else derives from it.
26
+
27
+ Given a name like *Files Section* or *Models List*, the full naming cascade is:
28
+
29
+ | Artifact | Convention | Example |
30
+ |---|---|---|
31
+ | Folder | kebab-case | `files-section/`, `models-list/` |
32
+ | Component identifier | camelCase | `filesSection`, `modelsList` |
33
+ | Types prefix | PascalCase | `FilesSection`, `ModelsList` |
34
+ | Template function | `{camelCase}Template` | `filesSectionTemplate` |
35
+ | Callback | `on{PascalCase}Created` | `onFilesSectionCreated` |
36
+
37
+ The identifier suffix describes what the component *is* from the consumer's perspective — typically the BUI element name:
38
+
39
+ | Identifier | Root element |
40
+ |---|---|
41
+ | `filesSection` | `<bim-panel-section>` |
42
+ | `collisionsTable` | `<bim-table>` |
43
+ | `modelsDropdown` | `<bim-dropdown>` |
44
+ | `actionsToolbar` | `<div>` |
45
+
46
+ > **Exception:** `<bim-panel-section>` uses the suffix `*Section`, not `*PanelSection`.
47
+
48
+ ---
49
+
50
+ ### Step 2 — Create the file structure
51
+
52
+ Each component lives in its own folder:
53
+
54
+ ```
55
+ files-section/
56
+ index.ts → template, onInstanceCreated (if needed), re-exports ./src
57
+ src/
58
+ index.ts → re-exports types and support files
59
+ types.ts → state types and any supporting types
60
+ ```
61
+
62
+ The `src/` subfolder holds types and any support files the template needs — helpers, constants, sub-types, etc. If `index.ts` is growing too long, move the extra logic into `src/`.
63
+
64
+ ---
65
+
66
+ ### Step 3 — Define types in `src/types.ts`
67
+
68
+ Every component needs at minimum these types:
69
+
70
+ ```ts
71
+ import * as OBC from "@thatopen/components"
72
+ import * as BUI from "@thatopen/ui"
73
+
74
+ export interface FilesSectionState {
75
+ components: OBC.Components
76
+ // ...additional state
77
+ }
78
+
79
+ export type FilesSectionComponent = BUI.StatefullComponent<FilesSectionState>
80
+ ```
81
+
82
+ - **`{ComponentName}State`** — the reactive state passed to the template on every render. Must always include `components: OBC.Components`.
83
+ - **`{ComponentName}Component`** — type alias for `BUI.StatefullComponent<{ComponentName}State>`. Used to type the template function.
84
+
85
+ Additional types depend on the component. For example, tables also need `{ComponentName}Data` — which must be a `type` alias, not an `interface`, as `BUI.Table<T>` requires a type alias:
86
+
87
+ ```ts
88
+ // ✗ Avoid — interface does not satisfy BUI.Table<T>
89
+ export interface MyTableData { Name: string }
90
+
91
+ // ✓ Correct
92
+ export type MyTableData = { Name: string }
93
+ ```
94
+
95
+ #### `components` is the only entry point to the engine
96
+
97
+ Inside a template, any engine component must be accessed via `components.get()`. State must never receive component instances directly:
98
+
99
+ ```ts
100
+ // ✗ Avoid — receiving a component instance directly
101
+ const myTemplate = (state: { highlighter: OBF.Highlighter }) => { ... }
102
+
103
+ // ✓ Correct — access everything through components
104
+ const myTemplate = ({ components }: FilesSectionState) => {
105
+ const highlighter = components.get(OBF.Highlighter)
106
+ }
107
+ ```
108
+
109
+ #### How much logic belongs in a template?
110
+
111
+ In the ideal case, a template is a thin facade: it reads state from BIM components via `components.get()` and triggers actions on them — nothing more.
112
+
113
+ In practice, some UI-level logic is fine to keep in the template:
114
+
115
+ - **Local coordination** — passing data from one sub-component to another within the same section
116
+ - **Transient UI state** — loading flags, intermediate form values before the user confirms
117
+ - **Orchestration** — calling several BIM components in sequence for a single user action
118
+
119
+ What doesn't belong in a template is **domain logic**: calculations, data transformations, business rules. If a template starts computing things beyond what's needed to render, that logic belongs in a BIM component instead.
120
+
121
+ The guiding question: *does this logic exist because the UI needs it, or because the domain requires it?* If the domain requires it, move it to a BIM component. See [`../connect-logic-to-ui.md`](../connect-logic-to-ui.md) for how UI templates connect to BIM components.
122
+
123
+ ---
124
+
125
+ ### Step 4 — Implement the template in `index.ts`
126
+
127
+ ### Rules
128
+
129
+ - **Always prefer BUI components over native HTML elements.** Before using `<select>`, `<input>`, `<button>`, `<dialog>`, etc., use the `paths.json` descriptions fetched in the working mode step to identify what's available. If you need the full list of available elements, fetch https://raw.githubusercontent.com/ThatOpen/engine_ui-components/refs/heads/main/packages/core/src/components/index.ts. Before implementing any BUI component, check [`./library-examples.md`](./library-examples.md) for an official example of the element you're working with — it's the best starting point for correct usage patterns. See [`./rendering-patterns.md`](./rendering-patterns.md) for general rendering patterns. For displaying and updating text dynamically, see [`./display-text.md`](./display-text.md).
130
+ - **Never use `<table>`.** The only acceptable element for tabular data is `<bim-table>`. A `<bim-table>` must always be its own standalone UI component — never defined inline inside another template. The consuming template instantiates the table component and composes it in. See [`./data-table.md`](./data-table.md) for how to build a table component.
131
+ - **For async actions** (loading states, async button handlers, etc.), see [`./async-actions.md`](./async-actions.md). **Before triggering a destructive or irreversible action**, always ask the user to confirm — see [`./confirmation-dialog.md`](./confirmation-dialog.md).
132
+ - **Do not configure element properties inside the template** that a consumer might want to override after instantiation — they will be reset on every render. Use `onInstanceCreated` instead.
133
+ - **Never nest sections.** If the user asks to group content, create independent templates — one per section. See [`./sections-layout.md`](./sections-layout.md).
134
+
135
+ #### Template
136
+
137
+ A `{ComponentName}Component` function that receives the current state and returns a `BUI.html` tagged template. Re-runs on every state update.
138
+
139
+ ```ts
140
+ export const filesSectionTemplate: FilesSectionComponent = ({ components }) => {
141
+ return BUI.html`<bim-panel-section></bim-panel-section>`
142
+ }
143
+ ```
144
+
145
+ #### `onInstanceCreated` — one-time setup
146
+
147
+ Called once when an instance is created. Use it for imperative, one-time configuration: event listeners, fixed element properties, etc. For building a create/edit form anchored to a button, see [`./inline-form.md`](./inline-form.md).
148
+
149
+ Receives the tuple `[element, updateFn, componentUtils]`:
150
+
151
+ - `element` — the created HTML element
152
+ - `updateFn` — triggers a re-render with a partial state update
153
+ - `componentUtils` — provides `getCurrentState()`, which always returns fresh state. Use it inside closures to avoid stale state captures:
154
+
155
+ ```ts
156
+ export const onFilesSectionCreated = (
157
+ [section, update, { getCurrentState }]: [
158
+ BUI.PanelSection,
159
+ BUI.UpdateFunction<FilesSectionState>,
160
+ BUI.ComponentUtils<FilesSectionState>
161
+ ]
162
+ ) => {
163
+ section.addEventListener("click", () => {
164
+ const { components } = getCurrentState() // always fresh state
165
+ const highlighter = components.get(OBF.Highlighter)
166
+ // ...
167
+ })
168
+ }
169
+ ```
170
+
171
+ ---
172
+
173
+ ### Step 5 — Export from the barrel
174
+
175
+ Re-export the component from the project's barrel index so it's accessible from a single import point:
176
+
177
+ ```ts
178
+ export * from "./files-section"
179
+ ```
180
+
181
+ When planning a new UI component, **always consider breaking it into smaller, reusable templates** rather than building a single monolithic one. Discuss the granularity with the developer — but the default stance is to favor small, composable units.
182
+
183
+ ---
184
+
185
+ ## See also
186
+
187
+ - [`./library-examples.md`](./library-examples.md) — Find official usage examples and discover what BUI provides
188
+ - [`./data-table.md`](./data-table.md) — Display data in a table, load columns, group rows
189
+ - [`./sections-layout.md`](./sections-layout.md) — Organize the layout of a panel section (zones, toolbar, status messages)
190
+ - [`./inline-form.md`](./inline-form.md) — Create or edit an entity in a popup form anchored to a button
191
+ - [`./confirmation-dialog.md`](./confirmation-dialog.md) — Ask for confirmation before a destructive or irreversible action
192
+ - [`./async-actions.md`](./async-actions.md) — Handle an async action with loading state on a button
193
+ - [`./rendering-patterns.md`](./rendering-patterns.md) — General rendering patterns (BUI.ref, requestUpdate, data-active)
194
+ - [`./display-text.md`](./display-text.md) — Display text, update text dynamically, `bim-label` vs `.label`
@@ -0,0 +1,129 @@
1
+ # Rendering Patterns
2
+
3
+ Common patterns that apply across all BUI components.
4
+
5
+ ## Lit and LitElement
6
+
7
+ All components in `@thatopen/ui` are Web Components built with [Lit](https://lit.dev/) and extend `LitElement`. Since `BUI.html` is Lit's `html` tagged template, the full Lit template syntax applies — property bindings, boolean attributes, event listeners, directives, etc.
8
+
9
+ ## requestUpdate()
10
+
11
+ Reassigning a reactive property (like `table.data`) automatically schedules a re-render. **Mutating a property in place** does not — call `requestUpdate()` manually in those cases:
12
+
13
+ ```ts
14
+ // Reassignment → automatic update
15
+ table.data = newData
16
+
17
+ // In-place mutation → manual update required
18
+ table.data.push(newRow)
19
+ table.requestUpdate()
20
+ ```
21
+
22
+ ## BUI.ref
23
+
24
+ `BUI.ref(callback)` provides an imperative reference to an element inside a `BUI.html` template. The callback is called once the element is mounted in the DOM:
25
+
26
+ ```ts
27
+ // Initialization
28
+ const onTableCreated = (e?: Element) => {
29
+ if (!e) return
30
+ const table = e as BUI.Table<MyTableData>
31
+ table.loadFunction = async () => { ... }
32
+ table.loadData(true)
33
+ }
34
+
35
+ return BUI.html`<bim-table ${BUI.ref(onTableCreated)}></bim-table>`
36
+ ```
37
+
38
+ ```ts
39
+ // Capture
40
+ let input: BUI.TextInput | undefined
41
+
42
+ const onInputCreated = (e?: Element) => {
43
+ if (!(e instanceof BUI.TextInput)) return
44
+ input = e
45
+ }
46
+
47
+ return BUI.html`
48
+ <bim-text-input ${BUI.ref(onInputCreated)}></bim-text-input>
49
+ <bim-button @click=${() => console.log(input?.value)}></bim-button>
50
+ `
51
+ ```
52
+
53
+ ## Creating Elements
54
+
55
+ When an element must be created imperatively outside of `BUI.html`, use `BUI.Component.create` instead of `document.createElement`:
56
+
57
+ ```ts
58
+ // ✗ Avoid
59
+ const button = document.createElement("bim-button")
60
+
61
+ // ✓ Correct
62
+ const button = BUI.Component.create(() => BUI.html`<bim-button label="Click me"></bim-button>`)
63
+ ```
64
+
65
+ ## Visual State with data-active
66
+
67
+ Use `toggleAttribute("data-active")` to reflect an element's active/inactive state visually:
68
+
69
+ ```ts
70
+ target.toggleAttribute("data-active") // toggle
71
+ target.toggleAttribute("data-active", true) // force active
72
+ target.toggleAttribute("data-active", false) // force inactive
73
+ ```
74
+
75
+ ## Event Handlers
76
+
77
+ ```ts
78
+ return BUI.html`
79
+ <bim-text-input @input=${onSearch}></bim-text-input>
80
+ <bim-button @click=${onClick}></bim-button>
81
+ `
82
+ ```
83
+
84
+ ## BUI.Manager.newRandomId()
85
+
86
+ Use when a template needs to associate DOM elements by ID. Since templates can be instantiated multiple times, hardcoded IDs would cause collisions:
87
+
88
+ ```ts
89
+ const inputId = BUI.Manager.newRandomId()
90
+
91
+ return BUI.html`
92
+ <label for=${inputId}>Name</label>
93
+ <input id=${inputId} type="text" />
94
+ `
95
+ ```
96
+
97
+ ## Triggering re-renders from inside a template (update)
98
+
99
+ `BUI.StatefullComponent` receives an optional second parameter `update`:
100
+
101
+ ```ts
102
+ export const myTemplate: BUI.StatefullComponent<MyState> = (state, update) => {
103
+ const onAdd = () => {
104
+ doSomething()
105
+ update() // re-render with same state
106
+ update({ count: 5 }) // re-render with partial state change
107
+ }
108
+ }
109
+ ```
110
+
111
+ Avoid calling `update` in response to changes that themselves trigger another `update` — this causes an infinite render loop.
112
+
113
+ ## Role-based rendering
114
+
115
+ When content depends on a user role or any runtime condition, use an optional variable before the `return`:
116
+
117
+ ```ts
118
+ let actionArea: BUI.TemplateResult | undefined
119
+ if (userHasPermission) {
120
+ actionArea = BUI.html`<bim-button label="Run Action" @click=${onRun}></bim-button>`
121
+ }
122
+
123
+ return BUI.html`
124
+ <bim-panel-section label="...">
125
+ ${content}
126
+ ${actionArea}
127
+ </bim-panel-section>
128
+ `
129
+ ```