Aller au contenu

Selections and the properties panel

The shell owns one properties panel on the right. Plugins never declare it: they publish what the user selected, and contribute forms per selection kind. Several plugins may contribute to the same kind (a plugin depending on Notes can add a “Planning” section to tasks); each form becomes a collapsible section, ordered by order.

1. Publish the selection (one per work-area tab; the panel follows the active tab, a closed tab loses its selection):

ctx.registerHandler("my-plugin/item.select", ({ payload }) => {
// Table: { rowId, rowIds }, graph: { nodeId, nodeIds } / { edgeId, edgeIds }.
const ids = payload.rowIds ?? payload.nodeIds ?? [];
ctx.selection.set(payload, ids.length ? {
kind: "my-plugin:item",
ids,
context: { docId: payload.docId }, // merged into every commit payload
} : null);
});
// Deleted entities leave every selection:
ctx.selection.prune("my-plugin:item", removedIds);

A compound selection publishes several entries at once, e.g. an edge and the flow it represents: ctx.selection.set(payload, [{ kind: "graph:edge", ids }, { kind: "my-plugin:flow", ids: [flowId], context }]). The properties panel renders the forms of every entry as separate sections; each section reads and commits only its own entry’s ids, kind and context. get / current return the first entry, getAll(scope) / currentAll every entry.

ctx.selection.get(viewContext) gives the selection of a view’s tab, so the view can highlight it: tables take activeIds, graphs selectedIds. Tables report Ctrl/Cmd-click (toggle), Shift-click (range) and Ctrl+A in rowIds; graphs report Ctrl/Cmd-tap and Shift-drag boxes in nodeIds / edgeIds. Single-item payload fields (rowId, nodeId, edgeId) remain.

2. Contribute a form in ui.json (or ctx.ui.registerPropertyForm):

"propertyForms": [{
"id": "task",
"selection": "task", // bare name => "<pluginId>:task"
"title": "panel.task",
"titleMany": "panel.taskMany",
"order": 10,
"values": { "ref": "selectedTasks" },
"event": "notes-tsx/task.property.set",
"fields": [
{ "key": "name", "type": "text", "label": "tasks.name", "multi": false },
{ "key": "color", "type": "color", "label": "tasks.color" }
]
}]
  • values is evaluated with { selection: { kind, ids, context }, ...context } and returns one record per selected id (or a single record).
  • With several records, a field showing the same value everywhere displays it; a differing one shows Multiple values until the user clicks to set them all. multi: false keeps a field out of bulk editing.
  • A commit emits event with { ...context, key, value, ids, kind, context }. Apply every id in that one handler: it is one transaction, so one undo step. Committing an unchanged value emits nothing.
  • panel.form / panel.forms still work for plugin-owned panels, but new plugins should use propertyForms.

The columns of a table and the fields of a propertyForms entry are a fixed list, or a value selector when they depend on the view:

{ "id": "tasks", "type": "table", "config": { "rows": { "ref": "taskRows" }, "columns": { "ref": "taskColumns" } } }
  • Register the source under values. It is evaluated with the placement context, as rows is; for fields, with the selection, as values is.
  • It returns the same declarations as a fixed list: titles and labels are i18n keys. An entry with no key or type is left out.
  • Return the same array while nothing changed: the table reads the source on every render.

A form field of type table embeds a table; its config is a whole table declaration (rows, columns, create / commit / delete events). Its rows binding receives the form context (the selection, docId…), and every event carries that context too, so the handler finds the parent in context.selection.ids[0]. With several items selected the field shows a hint instead of the table.

{ "key": "subtasks", "type": "table", "label": "tasks.subtasks", "multi": false,
"config": { "rows": { "ref": "subtaskRows" }, "creatable": true, "deletable": true,
"createEvent": "notes-tsx/subtask.add", "commitEvent": "notes-tsx/subtask.update",
"deleteEvent": "notes-tsx/subtask.remove",
"columns": [{ "key": "done", "type": "boolean", "title": "tasks.done", "editable": true },
{ "key": "name", "type": "text", "title": "tasks.name", "editable": true }] } }

Documentation sous licence CC BY 4.0 · © les contributeurs de SCFlow ·Retour à supplychainflow.org