@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,49 @@
1
+ # Type Conventions
2
+
3
+ All types for a component live in `src/types.ts` — domain interfaces, event payloads, serialized equivalents — so there is a single place to look.
4
+
5
+ ---
6
+
7
+ ## Runtime vs serialized
8
+
9
+ Components that implement `OBC.Serializable` need two versions of their data types: a runtime version that uses rich TypeScript types, and a serialized version that is JSON-safe.
10
+
11
+ The serialized type is named `Serialized{TypeName}`:
12
+
13
+ ```ts
14
+ // Runtime — uses Map, Set, Date, THREE.Color
15
+ export interface Activity {
16
+ index: number;
17
+ name: string;
18
+ color: THREE.Color;
19
+ items: OBC.ModelIdMap;
20
+ dates: Set<string>;
21
+ }
22
+
23
+ // Serialized — JSON-safe equivalent
24
+ export interface SerializedActivity {
25
+ index: number;
26
+ name: string;
27
+ color: string; // THREE.Color → hex string
28
+ items: [string, number[]][]; // ModelIdMap → tuple array
29
+ dates: string[]; // Set → array
30
+ }
31
+ ```
32
+
33
+ Conversion happens inside `export()` and `import()` (see [`./save-and-restore-state.md`](./save-and-restore-state.md)).
34
+
35
+ ---
36
+
37
+ ## Generics that require `type`, not `interface`
38
+
39
+ Some OBC and FRAGS generics enforce that their type parameter is a `type` alias. Passing an `interface` causes a TypeScript error. When in doubt, prefer `type` for types used as generic parameters:
40
+
41
+ ```ts
42
+ // ✗ Fails — interface not assignable to the generic constraint
43
+ export interface ActivityData { name: string; value: number }
44
+ const list = new FRAGS.DataMap<string, ActivityData>(); // error
45
+
46
+ // ✓ Correct
47
+ export type ActivityData = { name: string; value: number }
48
+ const list = new FRAGS.DataMap<string, ActivityData>();
49
+ ```
@@ -0,0 +1,56 @@
1
+ # User-Driven Object Creation
2
+
3
+ ## `OBC.Createable`
4
+
5
+ Implement when the component lets the user create objects in the scene through a multi-step interaction — click to start, click to confirm, Escape to cancel, click existing to delete.
6
+
7
+ The key characteristic is **statefulness between clicks**: `create()` is called on every user click, but the first click initiates the workflow and the second confirms it by calling `endCreation()` internally.
8
+
9
+ ```ts
10
+ export class ActivityTracker extends OBC.Component implements OBC.Createable {
11
+ private _temp: { isDragging: boolean; preview?: SomeObject } = {
12
+ isDragging: false,
13
+ };
14
+
15
+ create = () => {
16
+ if (!this.enabled) return;
17
+ if (!this._temp.isDragging) {
18
+ this._temp.isDragging = true;
19
+ this._temp.preview = new SomeObject();
20
+ return;
21
+ }
22
+ this.endCreation();
23
+ };
24
+
25
+ endCreation = () => {
26
+ if (!this._temp.preview) return;
27
+ this.list.add(this._temp.preview);
28
+ this._temp.isDragging = false;
29
+ this._temp.preview = undefined;
30
+ };
31
+
32
+ cancelCreation = () => {
33
+ this._temp.isDragging = false;
34
+ this._temp.preview?.dispose();
35
+ this._temp.preview = undefined;
36
+ };
37
+
38
+ delete = () => {
39
+ if (!this.world) return;
40
+ const casters = this.components.get(OBC.Raycasters);
41
+ const caster = casters.get(this.world);
42
+ const intersect = caster.castRayToObjects([...this._boundingBoxes]);
43
+ if (!intersect) return;
44
+ // identify and remove the hit object from list
45
+ };
46
+ }
47
+ ```
48
+
49
+ Wire up the Escape key in the component's `enabled` setter so `cancelCreation()` fires automatically when the component is disabled mid-flow:
50
+
51
+ ```ts
52
+ set enabled(value: boolean) {
53
+ this._enabled = value;
54
+ if (!value) this.cancelCreation();
55
+ }
56
+ ```
@@ -0,0 +1,54 @@
1
+ # CLI Setup and Authentication
2
+
3
+ Before you can scaffold or publish a That Open Platform app, two things must be in place: the `@thatopen/services` CLI installed globally, and a valid platform token for authentication.
4
+
5
+ ---
6
+
7
+ ## Step 1: Install or update the CLI
8
+
9
+ Run the following command in the terminal. This installs the CLI if it isn't present, or updates it to the latest version if it already is:
10
+
11
+ ```bash
12
+ npm i @thatopen/services@latest -g
13
+ ```
14
+
15
+ Once done, verify it works:
16
+
17
+ ```bash
18
+ thatopen --version
19
+ ```
20
+
21
+ If this returns a version number, the CLI is ready. If `npm` is not found, install Node.js first — download the LTS version from [https://nodejs.org](https://nodejs.org), then retry.
22
+
23
+ ---
24
+
25
+ ## Step 2: Get a platform token
26
+
27
+ Generate an access token from the platform:
28
+
29
+ 1. Go to [https://dev.platform.thatopen.com](https://dev.platform.thatopen.com)
30
+ 2. In the header, click **Data**
31
+ 3. Find the **Tokens** card and create a new token
32
+ 4. Enable the permissions: **Apps**, **Components** and **Storage**
33
+ 5. Copy the token
34
+
35
+ ---
36
+
37
+ ## Step 3: Authenticate
38
+
39
+ Once you have the token, run:
40
+
41
+ ```bash
42
+ npm run login -- --token <TOKEN>
43
+ ```
44
+
45
+ where `<TOKEN>` is the token you copied. This stores credentials globally at `~/.thatopen/config.json` and persists across sessions — you only need to do this once per machine.
46
+
47
+ ---
48
+
49
+ ## When to use this guide
50
+
51
+ Read this guide when:
52
+ - You are starting a new app and haven't confirmed the CLI is installed
53
+ - A `thatopen` command fails with a "command not found" or "not authenticated" error
54
+ - You have never built a platform app before on this machine
@@ -0,0 +1,74 @@
1
+ # Cloud components & automations
2
+
3
+ A **cloud component** is a piece of logic that runs **server-side** (Node.js) on the platform,
4
+ on the shared project context, via the API. Use them to automate real operations — clash checks,
5
+ validations, document generation — and to let apps offload heavy work.
6
+
7
+ ## Apps vs cloud components
8
+
9
+ | | Apps | Cloud components |
10
+ |---|---|---|
11
+ | **Runs in** | Browser (iframe on the platform) | Server (Node.js child process) |
12
+ | **Item type** | `APP` | `TOOL` |
13
+ | **Entry point** | Side effects in `main.ts` (renders UI) | `export async function main()` |
14
+ | **Build output** | IIFE `dist/bundle.js` (all deps bundled) | IIFE `dist/bundle.js` (only `@thatopen/services` externalized) |
15
+ | **Template** | `bim`, `default`, `test` | `cloud`, `cloud-test` |
16
+
17
+ ## Scaffold
18
+
19
+ ```bash
20
+ thatopen create my-component --template cloud
21
+ cd my-component
22
+ ```
23
+
24
+ The component is an `export async function main()` that runs on the server. The execution engine
25
+ injects these globals (do **not** import them; for `OBC`/`THREE`/`web-ifc` import them so the
26
+ bundler includes them):
27
+
28
+ | Global | Purpose |
29
+ |--------|---------|
30
+ | `thatOpenServices` | Authenticated `EngineServicesClient` |
31
+ | `executionParams` | Parameters passed by the caller |
32
+ | `executionContext` | `{ projectId?, executionId, toolId, toolVersion }` |
33
+ | `executionReporter` | `{ message(msg), error(msg), progress(pct) }` for live feedback |
34
+ | `OBC` | `@thatopen/components` — BIM engine (import it) |
35
+ | `THREE` | `three` — 3D math/geometry (import it) |
36
+ | `fs` | Node.js filesystem (import it) |
37
+
38
+ ## Run locally
39
+
40
+ ```bash
41
+ npm run run # build + test locally
42
+ npx thatopen run --params '{"inputFile":"model.ifc"}' # pass parameters
43
+ ```
44
+
45
+ `thatopen local-server` starts an API-compatible local execution server (default `:4001`) so an
46
+ app can call the component before it's deployed (set `client.localServerUrl`).
47
+
48
+ ## Authenticate & publish
49
+
50
+ ```bash
51
+ npm run login -- --token <your-token>
52
+ npm run publish
53
+ ```
54
+
55
+ ## Calling a component from an app
56
+
57
+ ```ts
58
+ const { executionId } = await client.executeComponent(componentId, { param: "value" }, versionTag?);
59
+ client.onExecutionProgress(executionId, (data) => {
60
+ // data.progressUpdate — percentage
61
+ // data.messageUpdate — status messages
62
+ });
63
+ ```
64
+
65
+ Include `projectId` in the execution params to scope the run to a project (the backend validates
66
+ the component is linked to that project). See [CONTEXT.md](../CONTEXT.md) for the permissions contract.
67
+
68
+ ## Automations (event-triggered components)
69
+
70
+ An **automation** is a cloud component that runs automatically in response to a platform **event**
71
+ (rather than being invoked by hand) — e.g. "run the clash check whenever a model is updated."
72
+ The component is the same; what differs is the trigger. Add it to a project, choose the event(s)
73
+ that fire it, and the platform executes it on the shared project context, reporting progress and
74
+ logs through `executionReporter`. Build and publish it exactly like any other cloud component above.
@@ -0,0 +1,113 @@
1
+ # Connect Logic to UI
2
+
3
+ Setup files are the bridge between custom BIM components (logic) and the platform UI. This is where event subscriptions, UI sync, and component wiring live — not in the component itself.
4
+
5
+ ## Structure
6
+
7
+ ```
8
+ setups/
9
+ ├── index.ts → barrel (always present)
10
+ ├── ui-manager.ts → fixed boilerplate (always present)
11
+ └── {custom}.ts → one per custom BIM component
12
+ ```
13
+
14
+ One file per component being initialized. Each file exports a single function that receives `components: OBC.Components`.
15
+
16
+ ---
17
+
18
+ ## `setups/ui-manager.ts` — Fixed boilerplate
19
+
20
+ This is where all UI templates get registered in the platform (see the UI-component guide in `docs/`: [./ui-components/overview.md](./ui-components/overview.md)). The file always has this shape — the content changes (which templates are registered), but the structure never does.
21
+
22
+ ### `CustomUIs` type
23
+
24
+ Maps every UI template to its element type and state. Add one entry per registered template:
25
+
26
+ ```ts
27
+ export type CustomUIs = {
28
+ filesSection: { type: BUI.PanelSection; state: FilesSectionState }
29
+ collisionsTable: { type: BUI.Table<CollisionsTableData>; state: CollisionsTableState }
30
+ // one entry per registered template
31
+ }
32
+ ```
33
+
34
+ ### `getUIManager` — typed accessor
35
+
36
+ The typed accessor for `UIManager`. Defined here because `CustomUIs` lives here. Used everywhere else in `setups/` to interact with the UI registry — never access `UIManager` directly:
37
+
38
+ ```ts
39
+ export const getUIManager = (components: OBC.Components) =>
40
+ components.get(UIManager<CustomUIs>)
41
+ ```
42
+
43
+ `getUIManager` is the **only** valid way to access `UIManager` in an app. Never use `components.get(UIManager)` directly — it drops the `CustomUIs` type parameter and loses all type information about the registered templates.
44
+
45
+ ```ts
46
+ // ✗ Avoid — loses the CustomUIs type
47
+ const uis = components.get(UIManager)
48
+
49
+ // ✓ Correct — fully typed
50
+ const uis = getUIManager(components)
51
+ ```
52
+
53
+ ### `uiManager` setup function
54
+
55
+ Registers each template and its optional `onInstanceCreated` callback, exported from the UI component:
56
+
57
+ ```ts
58
+ export const uiManager = (components: OBC.Components) => {
59
+ const uis = getUIManager(components)
60
+ uis.registerTemplate("filesSection", {
61
+ template: filesSectionTemplate,
62
+ onInstanceCreated: onFilesSectionCreated
63
+ })
64
+ uis.registerTemplate("collisionsTable", {
65
+ template: collisionsTableTemplate
66
+ })
67
+ }
68
+ ```
69
+
70
+ ### Composing UI components
71
+
72
+ Templates can embed other registered components by creating instances via `uis.custom.get()`:
73
+
74
+ ```ts
75
+ export const filesSectionTemplate: FilesSectionComponent = (state) => {
76
+ const uis = state.components.get(UIManager)
77
+ const [modelsList] = uis.custom.get("modelsList").create(state)
78
+ return BUI.html`
79
+ <bim-panel-section label="Files">
80
+ ${modelsList}
81
+ </bim-panel-section>
82
+ `
83
+ }
84
+ ```
85
+
86
+ ### Keeping UIs in sync with `updateInstances()`
87
+
88
+ To push a state update to all existing instances of a template, call `updateInstances()` from within a setup file — typically in response to engine events:
89
+
90
+ ```ts
91
+ const updateUIs = () => uis.custom.get("modelsList").updateInstances()
92
+ fragments.list.onItemSet.add(updateUIs)
93
+ fragments.list.onItemDeleted.add(updateUIs)
94
+ ```
95
+
96
+ ---
97
+
98
+ ## Custom setup files
99
+
100
+ Each setup file is the bridge between a custom BIM component and the platform (see the BIM-component guide in `docs/`: [./bim-components/overview.md](./bim-components/overview.md)). This is where wiring lands: event listeners, UI updates, and configuration go here — not in the component's constructor.
101
+
102
+ ```ts
103
+ // setups/qto-manager.ts
104
+ export const qtoManager = (components: OBC.Components) => {
105
+ const uis = getUIManager(components)
106
+ const qtoManager = components.get(QtoManager)
107
+
108
+ qtoManager.onStateChanged.add((states) => {
109
+ if (!states.includes("value")) return
110
+ uis.custom.get("qtosTable").updateInstances()
111
+ })
112
+ }
113
+ ```
@@ -0,0 +1,45 @@
1
+ # Previewing Apps During Development
2
+
3
+ That Open Platform apps can only be previewed correctly within the platform context — opening `localhost:4000` directly in the browser will just bring the app bundle. The app must be loaded from inside a project on the platform as it gives context.
4
+
5
+ ---
6
+
7
+ ## Step 1: Start the dev server
8
+
9
+ In the project root, run:
10
+
11
+ ```bash
12
+ npm run dev
13
+ ```
14
+
15
+ This invokes `thatopen serve` under the hood — the same CLI installed during setup. It performs a special build and serves the bundle on **port 4000**. Keep this process running throughout your development session.
16
+
17
+ ---
18
+
19
+ ## Step 2: Open the app in the platform
20
+
21
+ Once the dev server is running:
22
+
23
+ 1. Go to [https://dev.platform.thatopen.com](https://dev.platform.thatopen.com) and enter a project. If no project exists yet, create one first.
24
+ 2. In the project sidebar, click **Local App**.
25
+ 3. Click **Get Started**.
26
+
27
+ The platform will load the locally-running app from port 4000 and render it inside the project context, with full access to its models, data, and users.
28
+
29
+ The resulting URL follows this pattern:
30
+
31
+ ```
32
+ https://dev.platform.thatopen.com/dashboard/projects/{projectId}/apps/local-app
33
+ ```
34
+
35
+ where `{projectId}` is the ID of the project. Bookmark this URL to return quickly during development.
36
+
37
+ > The dev server must be running before opening this URL. If the bundle is not being served on port 4000, the platform will fail to load the app.
38
+
39
+ ---
40
+
41
+ ## When to read this guide
42
+
43
+ - When you want to see, run, or preview the app
44
+ - When you have run `npm run dev` and don't know what to do next
45
+ - When the app is not showing up in the platform
@@ -0,0 +1,35 @@
1
+ # Publishing an App
2
+
3
+ Publishing to the That Open Platform is a two-step process: authenticate first, then publish.
4
+
5
+ ## Step 1: Authenticate
6
+
7
+ ```bash
8
+ thatopen login
9
+ ```
10
+
11
+ This stores credentials globally at `~/.thatopen/config.json`. Authentication persists across sessions — you only need to do this once per machine.
12
+
13
+ **Options:**
14
+
15
+ | Flag | Purpose |
16
+ |---|---|
17
+ | `--token <token>` | Non-interactive login (CI, scripts) |
18
+ | `--local` | Store credentials in the project directory (`.thatopen`) instead of globally |
19
+
20
+ ## Step 2: Publish
21
+
22
+ ```bash
23
+ thatopen publish
24
+ ```
25
+
26
+ This builds the app and uploads it to the platform. The command handles the build step automatically unless told otherwise.
27
+
28
+ **Common options:**
29
+
30
+ | Flag | Purpose |
31
+ |---|---|
32
+ | `--name <name>` | Override the published app name |
33
+ | `--version-tag <tag>` | Tag the release (e.g. `v1.2.0`) |
34
+ | `--skip-build` | Skip the build step if the bundle is already built |
35
+ | `--app-id <id>` | Publish to a specific existing app |
@@ -0,0 +1,54 @@
1
+ # Scaffolding a New App
2
+
3
+ When starting a new platform app from scratch, **always use the CLI to scaffold the project — never create the files manually.** The CLI produces the exact project structure these docs describe, correctly configured and with dependencies installed. Writing the scaffold by hand defeats the purpose of the tool and risks inconsistencies.
4
+
5
+ ## Command
6
+
7
+ ```bash
8
+ thatopen create <app-name>
9
+ ```
10
+
11
+ Use `.` as the name to scaffold in the current directory:
12
+
13
+ ```bash
14
+ thatopen create .
15
+ ```
16
+
17
+ This copies the template files into the target directory and runs `npm install` automatically.
18
+
19
+ ## Templates
20
+
21
+ Pass `--template <name>` to choose a template. The default is `bim`.
22
+
23
+ | Template | When to use |
24
+ |---|---|
25
+ | `bim` (default) | Standard BIM viewer app — Three.js viewport, BIM viewer, platform UI components. This is the right starting point for almost every app. |
26
+ | `default` | Minimal shell — just shows platform context. Use only when you explicitly want to start from scratch without any viewer. |
27
+
28
+ If no template is specified, use `bim`.
29
+
30
+ ## What the scaffold produces
31
+
32
+ The `bim` template generates the full structure described in the **Project Structure** section of [./app-architecture.md](./app-architecture.md):
33
+
34
+ ```
35
+ src/
36
+ ├── bim-components/
37
+ ├── ui-components/
38
+ ├── setups/
39
+ ├── app.ts
40
+ ├── globals.ts
41
+ └── main.ts
42
+ ```
43
+
44
+ All platform built-ins (`AppManager`, `UIManager`, `ViewportsManager`) are already wired up in the generated `main.ts`. There is nothing to manually wire at the entry point — just extend from there.
45
+
46
+ ## Starting the dev server
47
+
48
+ After scaffolding, start the local dev server:
49
+
50
+ ```bash
51
+ thatopen serve
52
+ ```
53
+
54
+ This launches an esbuild watch process that rebundles on every file change and serves the app with live reload. No configuration needed.
@@ -0,0 +1,18 @@
1
+ # Async Actions
2
+
3
+ ## Async button handler
4
+
5
+ When a button triggers an async operation, set `target.loading = true` at the start and `false` when done — including in error paths:
6
+
7
+ ```ts
8
+ const onClick = async ({ target }: { target: BUI.Button }) => {
9
+ target.loading = true
10
+ try {
11
+ await doSomething()
12
+ } finally {
13
+ target.loading = false
14
+ }
15
+ }
16
+ ```
17
+
18
+ Always restore `target.loading = false` in error paths. If an exception is thrown and loading is not reset, the button stays stuck in the loading state.
@@ -0,0 +1,39 @@
1
+ # Confirmation Dialog
2
+
3
+ The standard way to ask the user to confirm a destructive or irreversible action is a `contextMenuTemplate` on a `bim-button`. The menu opens lazily when clicked and contains Confirm / Cancel buttons.
4
+
5
+ ## Setup
6
+
7
+ `contextMenuTemplate` must always be assigned via `BUI.ref` — never inline in `BUI.html`:
8
+
9
+ ```ts
10
+ const onBtnCreated = (e?: Element) => {
11
+ if (!e) return
12
+ const btn = e as BUI.Button
13
+ btn.contextMenuTemplate = () => {
14
+ const onConfirm = async ({ target }: { target: BUI.Button }) => {
15
+ target.loading = true
16
+ await doSomething()
17
+ target.loading = false
18
+ BUI.ContextMenu.removeMenus()
19
+ }
20
+
21
+ const onCancel = () => {
22
+ BUI.ContextMenu.removeMenus()
23
+ }
24
+
25
+ return BUI.html`
26
+ <bim-context-menu>
27
+ <bim-button @click=${onConfirm} label="Confirm"></bim-button>
28
+ <bim-button @click=${onCancel} label="Cancel"></bim-button>
29
+ </bim-context-menu>
30
+ `
31
+ }
32
+ }
33
+
34
+ return BUI.html`<bim-button ${BUI.ref(onBtnCreated)} icon="delete"></bim-button>`
35
+ ```
36
+
37
+ ## Closing the menu
38
+
39
+ Call `BUI.ContextMenu.removeMenus()` after the action completes (or is cancelled) to dismiss the popup. Without it, the menu stays open after the user clicks.