lineadraw 0.9.0 → 0.10.0

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.
package/README.md CHANGED
@@ -113,7 +113,9 @@ appended when missing):
113
113
  configuration, never written to the file.
114
114
  - **Scripts**: `await runScript(source, inputs?, params?)` runs a
115
115
  `defineCommand` module (the same one the editor palette runs) headlessly
116
- as ONE undo step — its declared `inputs` (points) are supplied up front,
116
+ as ONE undo step — its declared `inputs` are supplied up front (by name
117
+ for an inputs table: `{ origin: [0, 0], doors: [id] }`; points in order
118
+ for labels),
117
119
  its declared `params` by name (`{ count: 3 }`; omitted ones take their
118
120
  defaults, unknown names and wrong types throw); `runUserCommand(id, inputs?,
119
121
  params?)` runs one of the document's own commands. Returns the script's
@@ -260,16 +262,32 @@ export default defineBlock({
260
262
  params: [
261
263
  { name: "kind", type: "enum", default: "rock", options: ["rock", "ground"] },
262
264
  ],
263
- place: ["Start point", "End point"], // one point pick per label, in order
264
- draw: ({ params, inputs: [start, end] }) => {
265
+ // What the user picks at insertion, read by name in `draw`.
266
+ inputs: [
267
+ { name: "start", type: "point", label: "Start point" },
268
+ { name: "end", type: "point", label: "End point" },
269
+ ],
270
+ draw: ({ params, inputs: { start, end } }) => {
265
271
  params.kind; // "rock" | "ground" — inferred from the params table
266
272
  return [{ type: "line", a: start, b: end }];
267
273
  },
268
274
  });
269
275
  ```
270
276
 
271
- Notable optional members: `place` may instead be an interactive
272
- `({ params, pickPoint, pickObject })` function run once at insertion;
277
+ Input kinds: `point`; `points` — a chain picked until Enter (`min`,
278
+ `max`, `closed` for a polygon) arriving as `Vec2[]`; `object` / `objects`
279
+ — picked objects filtered by `types` (`["line", "polyline"]`), arriving in
280
+ `draw` as the objects themselves (re-evaluated when they change; a
281
+ deleted one is `null` / dropped). `point` and `object` may be `optional`
282
+ (Enter skips). The first point is the pivot. Instances store their inputs
283
+ by name, so reordering or adding inputs never breaks placed instances —
284
+ removing or renaming one does. (The older `place: ["Start", "End"]` labels
285
+ form, with `draw` reading `inputs` as an array, still works.)
286
+
287
+ Notable optional members: `place` — an interactive
288
+ `({ params, pickPoint, pickPoints, pickObject, pickObjects })` function run
289
+ once at insertion, for conditional or chained picks; with an `inputs`
290
+ table it returns the inputs by name;
273
291
  `dependencies` lists ids of blocks your `draw` emits instances of (installs
274
292
  pull them along); `paramVisibility({ params })` hides property rows;
275
293
  `previewInputs` curates the inputs used for thumbnails. Units are
@@ -294,6 +312,26 @@ Parameter types (the same rows serve a command's `params`):
294
312
  - `label` — a section heading (no value); `row` — 2–3 compact params on one
295
313
  panel row (`items`, each label a short prefix like "W"); its items are
296
314
  ordinary flat params (`params.w`).
315
+ - `data` — structured data the block owns: any JSON value, typed by its
316
+ default (`default: {} as Record<string, Wall>`). It has no Properties row;
317
+ handles, handle menus and actions edit it (agents set it like any param).
318
+
319
+ Geometry helpers come from `"lineadraw/helpers"` — vector math
320
+ (`add`, `sub`, `scale`, `rotate`, `polar`, …) and polygon booleans:
321
+ `union(...polygons)`, `difference(subject, ...clips)`,
322
+ `intersection(...polygons)`. A polygon is a ring of points, or rings with
323
+ holes (first the outer boundary). The result is a flat list of rings,
324
+ ready for closed polylines or ONE hatch, which fills even-odd so holes
325
+ stay holes:
326
+
327
+ ```ts
328
+ const solid = union(...walls.map(wallRect)); // overlapping walls → clean outlines
329
+ return [
330
+ { type: "hatch", loops: solid.map((points) => ({ points })),
331
+ fill: { kind: "pattern", name: "ANSI31", angle: 0, scale: 20 } },
332
+ ...solid.map((points) => ({ type: "polyline", points, closed: true })),
333
+ ];
334
+ ```
297
335
 
298
336
  Every param may carry a `hint` (the label's tooltip). An instance that
299
337
  overrides a param shows "Reset" beside it in Properties, back to your
@@ -302,10 +340,94 @@ default. `draw` also receives
302
340
  empty or unknown id → the first), for blocks that lay out with real text
303
341
  sizes.
304
342
 
343
+ #### Hosted blocks
344
+
345
+ A block can LIVE ON another instance: a door on a floor plan. Give it a
346
+ `host` input; it then draws in the host's frame (moving, rotating and
347
+ copying with it, and deleted with it), and the two talk through
348
+ `expose`, JSON each publishes from its OWN params:
349
+
350
+ ```ts
351
+ // The plan publishes its walls and cuts them where its openings say.
352
+ expose: ({ params }) => ({ walls: wallTable(params) }),
353
+ draw: ({ params, hosted }) => {
354
+ const cuts = hosted.map((h) => h.exposed?.cut).filter(Boolean);
355
+ return wallSolids(params, cuts);
356
+ },
357
+
358
+ // A door: picked by clicking a plan's wall; `attach` turns the click
359
+ // (plan-local) into its params.
360
+ inputs: [{ name: "plan", type: "host", types: ["@my-blocks/floor-plan"] }],
361
+ expose: ({ params }) => ({ cut: { wall: params.wall, at: params.at, width: params.width } }),
362
+ attach: ({ host, point }) => nearestWall(host.walls, point), // → { wall, at }
363
+ draw: ({ params, host }) => doorLeaf(host.walls[params.wall], params),
364
+ ```
365
+
366
+ `expose` never sees the host or what's on it, so the two can't loop
367
+ (ADR 0008). What a host publishes and what its openings read is a
368
+ contract between the modules: document it.
369
+
370
+ A hosted block has no host in Library / marketplace previews. Give it a
371
+ stand-in with `previewHost`: the JSON a host's `expose` would return.
372
+ The preview runs `attach` as if the host were clicked at its origin, and
373
+ `draw` gets the stand-in as `host` (the save-time dry run uses it too):
374
+
375
+ ```ts
376
+ previewHost: { preview: true, walls: { A: { a: [-1500, 0], b: [1500, 0], t: 200 } } },
377
+ // …and in draw, `host.preview` can add context: a stretch of wall.
378
+ ```
379
+
380
+ #### Handles and actions
381
+
382
+ `handles({ params, inputs })` returns the handles a selected instance
383
+ shows, in block-local coordinates. It re-runs as values change, so a
384
+ handle can sit at the current width:
385
+
386
+ ```ts
387
+ handles: ({ params }) => [
388
+ // Drag along local X: the distance becomes `width` (clamped to its min/max).
389
+ { id: "width", at: [params.width, 0], shape: "triangle",
390
+ along: { origin: [0, 0], direction: [1, 0] }, param: "width" },
391
+ // A button: one click, one undo step.
392
+ { id: "flip", at: [params.width / 2, 150], fixed: true, icon: "flip-horizontal",
393
+ click: ({ params }) => ({ params: { swing: params.swing === "left" ? "right" : "left" } }) },
394
+ // A line handle (shown while hovered) with a hover menu.
395
+ { id: "wall", kind: "line", from: [0, 0], to: [params.width, 0], fixed: true,
396
+ menu: ["thickness",
397
+ { field: { type: "boolean", label: "Enabled" }, value: !params.off,
398
+ set: (on) => ({ params: { off: !on } }) }],
399
+ actions: [{ label: "Reset", icon: "x", run: () => ({ params: { off: false } }) }] },
400
+ ],
401
+ // The instance's context menu (right-click): pick a point, then run.
402
+ actions: [
403
+ { label: "Add mark", pick: "point",
404
+ run: ({ params, point }) => ({ params: { marks: [...params.marks, [point.x, point.y]] } }) },
405
+ ],
406
+ ```
407
+
408
+ A handle has ONE drag behaviour: `param` (the constrained value → a number
409
+ param), `input` (moves a point input, replacing its default handle), or
410
+ `drag` (a function returning a patch). Otherwise it is `fixed`. The
411
+ constraint is free, `along` an axis (value = distance) or `around` a
412
+ centre (value = angle in radians). A line handle moves along its normal by
413
+ default. `click` turns it into a button. Shapes are `square`, `circle`,
414
+ `diamond`, `triangle`, `arrow`, `double-arrow`, `plus` and `minus`, and `icon` names a
415
+ lucide glyph from a small set. Handle functions and actions return a PATCH
416
+ — `{ params?, inputs? }`, with inputs by name and block-local — and never
417
+ mutate anything. Each applies as one undo step. The save-time dry run
418
+ checks every handle record.
419
+
305
420
  ### Writing a command
306
421
 
307
422
  A command is a macro with a single `defineCommand` default export. Its
308
- whole run is one undo step; Esc during a pick reverts cleanly. A
423
+ whole run is one undo step; Esc during a pick reverts cleanly. Its
424
+ `inputs` table is the same one blocks declare; `object` / `objects` inputs
425
+ take the current selection when it fits (types and count) and arrive as
426
+ ids. To ask more DURING the run (the next question depends on the last
427
+ answer), use `editor.pickPoint` / `pickObject` / `prompt`, or
428
+ `editor.ask(paramsTable, { title })` for several fields in one dialog —
429
+ and declare `requiresEditor: true`, so CLI / agent runs refuse the command
430
+ up front instead of failing halfway. A
309
431
  `params` table (the same rows a block's `params` uses) is asked for in a
310
432
  dialog before the run and arrives typed as `ctx.params`; headless callers
311
433
  pass it by name.
@@ -317,11 +439,12 @@ import { defineCommand } from "lineadraw";
317
439
  export default defineCommand({
318
440
  id: "@my-blocks/circle-here",
319
441
  name: "Circle here",
320
- inputs: ["Center point"],
442
+ inputs: [{ name: "center", type: "point", label: "Center point" }],
321
443
  params: [{ name: "radius", type: "number", default: 10, min: 0.1 }],
322
- // `inputs` are picked in the editor (or passed by a headless caller),
323
- // `params` asked for in a dialog; `editor` is absent when headless.
324
- run: async ({ document, editor, inputs: [center], params }) => {
444
+ // `inputs` are picked in the editor (or passed by name by a headless
445
+ // caller: { center: [0, 0] }), `params` asked for in a dialog; `editor`
446
+ // is absent when headless.
447
+ run: async ({ document, editor, inputs: { center }, params }) => {
325
448
  document.add([{ type: "circle", center, radius: params.radius }]);
326
449
  editor?.toast("Done", "success");
327
450
  },
@@ -371,11 +494,11 @@ Collection name and owner come from your package.json:
371
494
 
372
495
  ### Using a marketplace in the editor
373
496
 
374
- In [the editor](https://lineadraw.com): **Library → Marketplace tab → the
375
- gear button (Manage marketplaces…) → Add a marketplace** and enter
376
- `owner/repo` (or `owner/repo@branch`). For a private repository,
497
+ In [Linea](https://lineadraw.com): **App settings → Marketplaces → Add a
498
+ marketplace** (the gear in Library → Marketplace opens the same tab) and
499
+ enter `owner/repo` (or `owner/repo@branch`). For a private repository,
377
500
  create a fine-grained personal access token with read-only Contents access
378
- to just that repository and paste it in the add dialog — it is stored only
501
+ to just that repository and paste it under "Add an access token" — it is stored only
379
502
  on that device. Installing embeds the module (and its declared dependency
380
503
  closure) in the document, so drawings keep working offline and travel with
381
504
  the file.