@threadlabs/looma 0.29.7 → 0.29.8

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.
@@ -0,0 +1,8 @@
1
+ import type { NodeViewRenderer } from "@tiptap/core";
2
+ /**
3
+ * A pointer-sized document divider drawn by Separator, selectable while the editor is editable.
4
+ * @lifecycle The editor owns the node and its deletion. Separator connects while ProseMirror keeps its
5
+ * element in the document and disconnects once ProseMirror removes it; destroying the view stops
6
+ * following the editor's editability.
7
+ */
8
+ export declare const dividerNodeView: NodeViewRenderer;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@threadlabs/looma",
3
- "version": "0.29.7",
3
+ "version": "0.29.8",
4
4
  "type": "module",
5
5
  "description": "Looma components as HTML, Vue, and plain DOM, with design tokens.",
6
6
  "license": "MIT",
@@ -10965,4 +10965,59 @@ html[data-ui-input-modality="touch"]
10965
10965
  width: 3px;
10966
10966
  background: var(--ui-affordance-direct-color, var(--ui-accent-solid, #5b55d6));
10967
10967
  }
10968
+ /* Generated by HTML Next 1.0.0-alpha.55. Do not edit. */
10969
+ @supports (-moz-appearance: none) {
10970
+ :where([data-component], [data-slotted]) { --html-next-scope-owner: boundary; }
10971
+ :where([data-slotted]:not([data-component])) { --html-next-scope-owner: projected !important; }
10972
+ }
10973
+ @supports (-moz-appearance: none) {
10974
+ [data-component~="ui-separator"] { --html-next-scope-owner: "ui-separator" !important; }
10975
+ }
10976
+ @scope ([data-component~="ui-separator"]) to ([data-component], [data-slotted]) {
10977
+ /* This component's hooks style the element they are set on, and never a nested one. */
10978
+ @property --ui-separator-color {
10979
+ syntax: "*";
10980
+ inherits: false;
10981
+ }
10982
+
10983
+ /* The root is the hr, so the rules name :host; a bare hr matches only a descendant. */
10984
+ :scope:where([data-component~="ui-separator"]) {
10985
+ box-sizing: border-box;
10986
+ align-self: stretch;
10987
+ margin: 0;
10988
+ border: 0;
10989
+ border-block-start: var(--ui-border-width) solid var(--ui-separator-color, var(--_separator-color, var(--ui-border-divider)));
10990
+ }
10991
+
10992
+ :scope:where([data-component~="ui-separator"])[data-ui-separator-state~="emphasis=strong"] {
10993
+ --_separator-color: var(--ui-border-strong);
10994
+ }
10995
+
10996
+ :scope:where([data-component~="ui-separator"])[aria-orientation="vertical"] {
10997
+ block-size: auto;
10998
+ border-block-start: 0;
10999
+ border-inline-start: var(--ui-border-width) solid var(--ui-separator-color, var(--_separator-color, var(--ui-border-divider)));
11000
+ }
11001
+
11002
+ :scope:where([data-component~="ui-separator"])[data-ui-separator-state~="selectable"] {
11003
+ min-block-size: var(--ui-control-size-sm);
11004
+ border: 0;
11005
+ background: linear-gradient(var(--ui-separator-color, var(--ui-border-divider)), var(--ui-separator-color, var(--ui-border-divider))) center / 100%
11006
+ var(--ui-border-width) no-repeat;
11007
+ cursor: pointer;
11008
+ }
11009
+ :scope:where([data-component~="ui-separator"])[data-ui-separator-state~="selectable"]:hover {
11010
+ background-color: var(--ui-surface-hover);
11011
+ }
11012
+ :scope:where([data-component~="ui-separator"])[data-ui-separator-state~="selected"] {
11013
+ outline: var(--ui-focus-width) solid var(--ui-focus-ring);
11014
+ outline-offset: var(--ui-focus-width);
11015
+ border-radius: var(--ui-radius-sm);
11016
+ }
11017
+ }
11018
+ @scope ([data-component~="ui-separator"]) to ([data-component]) {
11019
+ /* This component's hooks style the element they are set on, and never a nested one. */
11020
+
11021
+ /* The root is the hr, so the rules name :host; a bare hr matches only a descendant. */
11022
+ }
10968
11023
  /*$vite$:1*/
@@ -1,15 +1,17 @@
1
1
  import { computed, createApp, defineComponent, h, nextTick, onBeforeUnmount, onMounted, onUpdated, reactive, ref, shallowRef, useId, watch } from "vue";
2
2
  import { DEFAULT_MENTION_RESULT_LIMIT, LOOMA_ICONS, createLoomaMentionExtension, createLoomaSlashCommandExtension, getActiveTableUiState, getDefaultEditorExtensions, getDefaultSlashCommands, getTableOfContentsEntries, getVisualViewportRect, handleTableAction, handleTableOverlayAction, measureTableOverlayGeometry, normalizeActiveTableColumnWidths, normalizeTableOfContentsConfig } from "@threadlabs/looma/editor";
3
- import { Button, Callout, Card, Checkbox, Cluster, Disclosure, EditorBlockActions, EditorChipEditor, EditorCodeLanguage, EditorImageToolbar, EditorInsertTableGrid, EditorLinkCard, EditorLinkForm, EditorMentionMenu, EditorMentionMenuItem, EditorSlashMenu, EditorSlashMenuGroup, EditorSlashMenuItem, EditorSurface, EditorTableContextMenu, EditorTableOverlay, EditorTableToolbar, EditorToolbar, FormField, Icon, IconButton, Image, Input, List, Menu, MenuItem, Popover, Separator, Stack, StatusMessage, Text, Tooltip } from "@threadlabs/looma/vue";
3
+ import { Button, Callout, Card, Checkbox, Cluster, Disclosure, EditorBlockActions, EditorChipEditor, EditorCodeLanguage, EditorImageToolbar, EditorInsertTableGrid, EditorLinkCard, EditorLinkForm, EditorMentionMenu, EditorMentionMenuItem, EditorSlashMenu, EditorSlashMenuGroup, EditorSlashMenuItem, EditorSurface, EditorTableContextMenu, EditorTableOverlay, EditorTableToolbar, EditorToolbar, FormField, Icon, IconButton, Image, Input, List, Menu, MenuItem, Popover, Stack, StatusMessage, Text, Tooltip } from "@threadlabs/looma/vue";
4
4
  import { Editor, Extension, Node as Node$1, getText, getTextSerializersFromSchema, isNodeSelection, mergeAttributes, nodeInputRule, posToDOMRect } from "@tiptap/core";
5
5
  import { Editor as Editor$1 } from "@tiptap/vue-3";
6
6
  import { AllSelection, NodeSelection, Plugin, PluginKey, TextSelection } from "@tiptap/pm/state";
7
7
  import { Decoration, DecorationSet } from "@tiptap/pm/view";
8
8
  import { createLowlight } from "lowlight";
9
+ import { RequestEvent, updateComponentProps } from "@nextwebwg/html-next/runtime";
9
10
  import "@tiptap/pm/tables";
10
11
  import "@tiptap/pm/transform";
11
12
  import { closeHistory } from "@tiptap/pm/history";
12
13
  import { DOMParser, DOMSerializer, Fragment as Fragment$1 } from "@tiptap/pm/model";
14
+ import { acceptProps, attachGeneratedController, checkBoolean, checkFormat, formatOf, hostState, keywordFormat, manageProps, toAttribute, writeAttribute } from "@nextwebwg/html-next/generated-runtime";
13
15
  export * from "@threadlabs/looma/editor";
14
16
  //#region src/vue/editor/primitives.ts
15
17
  /** Each Vue editor component's element tag, for tooling that enumerates them. */
@@ -1224,179 +1226,6 @@ function createImageUploads(options) {
1224
1226
  };
1225
1227
  }
1226
1228
  //#endregion
1227
- //#region ../../node_modules/.pnpm/@nextwebwg+html-next@1.0.0-alpha.55/node_modules/@nextwebwg/html-next/dist/requests.js
1228
- /**
1229
- * Requests: behavior a component asks its consumer for, answered through a native event's
1230
- * `respondWith` (https://nextwebwg.org/declarative-components/javascript#requests).
1231
- *
1232
- * Every HTML Next bundle on a page (the live runtime, compiled modules and framework helpers) keeps
1233
- * its requests in one record on the document, so any consumer answers any component, and one
1234
- * document listener relays late responders. Converted framework components carry this module's
1235
- * source (scripts/generate-literal-source.ts), so it imports nothing.
1236
- */
1237
- var ports = /* @__PURE__ */ new WeakMap();
1238
- var port = (event) => {
1239
- let found = ports.get(event);
1240
- if (found === void 0) ports.set(event, found = {});
1241
- return found;
1242
- };
1243
- /**
1244
- * A request as its responders receive it: the nearest one answers with `respondWith`. Its static
1245
- * `respond`, `request` and `hasResponder` are how code without a `host` takes part
1246
- * (https://nextwebwg.org/declarative-components/javascript#outside-a-controller). They never read
1247
- * `this`, so `const { respond, request, hasResponder } = RequestEvent` works.
1248
- */
1249
- var RequestEvent = class extends CustomEvent {
1250
- /** Aborts when the asker no longer needs the answer, or its component disconnects. */
1251
- signal;
1252
- constructor(type, init) {
1253
- super(type, init);
1254
- this.signal = init.signal;
1255
- }
1256
- /** Answer the request with a value or a promise of one; the event stops propagating. */
1257
- respondWith(answer) {
1258
- const recorded = port(this);
1259
- if (this.eventPhase === 0 || recorded.a !== void 0) throw new DOMException(recorded.a === void 0 ? "A request is answered while it is dispatched." : "The request already has an answer.", "InvalidStateError");
1260
- recorded.a = [answer];
1261
- this.stopImmediatePropagation();
1262
- }
1263
- /** Report progress to the asker while the request is pending. */
1264
- progress(loaded, total) {
1265
- const report = this.signal.aborted ? void 0 : ports.get(this)?.p;
1266
- try {
1267
- report?.(loaded, total);
1268
- } catch (error) {
1269
- reportError(error);
1270
- }
1271
- }
1272
- /**
1273
- * Answers `name` requests that reach `target` with `answer`, records it as a known responder, then
1274
- * announces it so a component whose request went unanswered can ask again. Returns a function that
1275
- * removes it.
1276
- */
1277
- static respond(target, name, answer) {
1278
- const listener = (event) => {
1279
- if ("respondWith" in event) answerRequest(event, answer);
1280
- };
1281
- target.addEventListener(name, listener);
1282
- const forget = knownResponder(target, name);
1283
- target.dispatchEvent(new CustomEvent("responder", {
1284
- bubbles: true,
1285
- detail: { name }
1286
- }));
1287
- return () => {
1288
- target.removeEventListener(name, listener);
1289
- forget();
1290
- };
1291
- }
1292
- /**
1293
- * Asks `name` at `target` with the request steps `host.request` uses, with no declaration to check
1294
- * the detail or the answer against, no connection signal, and nothing remembered for late responders.
1295
- */
1296
- static request(target, name, detail, options) {
1297
- return dispatchRequest(target, target.nodeType ? target : null, name, detail ?? null, options?.signal ?? new AbortController().signal, options?.progress);
1298
- }
1299
- /** Whether a known responder for `name` is on the path a request from `target` takes, now. */
1300
- static hasResponder(target, name) {
1301
- return knownCount(target, name) > 0;
1302
- }
1303
- };
1304
- /** Answers a request event with a responder function, as `RequestEvent.respond` and framework function props do. */
1305
- function answerRequest(event, responder) {
1306
- const request = event;
1307
- request.respondWith(responder(request.detail, {
1308
- signal: request.signal,
1309
- progress: (loaded, total) => request.progress(loaded, total)
1310
- }));
1311
- }
1312
- var runtimeKey = Symbol.for("@nextwebwg/html-next.runtime.v1");
1313
- var documentRecord = (document) => document[runtimeKey] ??= {};
1314
- /** A target's document: a node's owner, a window's document, or a target outside the DOM itself. */
1315
- var recordOf = (target) => documentRecord(target.nodeType === 9 ? target : target.ownerDocument ?? target.document ?? target);
1316
- var requestState = (record) => record.requests ??= { g: /* @__PURE__ */ new WeakMap() };
1317
- /**
1318
- * Records a responder for `name` on `target` that `hasResponder` can see, until the returned function
1319
- * runs. `own` counts the entries a controller's `host.on` adds on its own root, which its own
1320
- * `host.hasResponder` does not count.
1321
- */
1322
- function knownResponder(target, name, own) {
1323
- const record = recordOf(target);
1324
- const known = record.k ??= /* @__PURE__ */ new WeakMap();
1325
- const names = known.get(target) ?? known.set(target, []).get(target);
1326
- const change = (add) => {
1327
- for (const list of own === void 0 ? [names] : [names, own]) if (add) list.push(name);
1328
- else list.splice(list.indexOf(name), 1);
1329
- record.w?.forEach((changed) => changed(name));
1330
- };
1331
- change(true);
1332
- let held = true;
1333
- return () => {
1334
- if (held) {
1335
- held = false;
1336
- change(false);
1337
- }
1338
- };
1339
- }
1340
- /** The next object on a composed event's path: an assigned slot, a parent, a shadow host, the window. */
1341
- var parentOf = (node) => node.nodeType === 9 ? node.defaultView : node.nodeType === 11 ? node.host : node.nodeType ? node.assignedSlot ?? node.parentNode : null;
1342
- /** How many of `names` are `name`. */
1343
- var countOf = (names, name) => names?.filter((known) => known === name).length ?? 0;
1344
- /** How many known responders for `name` are on the path a request from `target` takes. */
1345
- function knownCount(target, name) {
1346
- const known = recordOf(target).k;
1347
- let count = 0;
1348
- if (known !== void 0) for (let node = target; node; node = parentOf(node)) count += countOf(known.get(node), name);
1349
- return count;
1350
- }
1351
- /** Settles with `value`, or rejects with the signal's reason when it aborts first; a late value is discarded. */
1352
- function settle(value, signal) {
1353
- return new Promise((resolve, reject) => {
1354
- const abort = () => reject(signal.reason);
1355
- if (signal.aborted) abort();
1356
- signal.addEventListener("abort", abort, { once: true });
1357
- Promise.resolve(value).then((result) => {
1358
- signal.removeEventListener("abort", abort);
1359
- resolve(result);
1360
- }, (error) => {
1361
- signal.removeEventListener("abort", abort);
1362
- reject(error);
1363
- });
1364
- });
1365
- }
1366
- /**
1367
- * The request steps every asker shares: wait for the controllers of components at `from` and around
1368
- * it, dispatch the `RequestEvent` at `target`, and settle with the answer, or `null` when nobody
1369
- * answered. `answered` hears, during the dispatch, whether anyone did.
1370
- */
1371
- async function dispatchRequest(target, from, name, detail, signal, progress, answered) {
1372
- signal.throwIfAborted();
1373
- const state = requestState(recordOf(target));
1374
- for (;;) {
1375
- const waits = [];
1376
- for (let node = from; node !== null; node = node.parentNode ?? node.host ?? null) {
1377
- const gate = state.g.get(node);
1378
- if (gate !== void 0) waits.push(gate);
1379
- }
1380
- if (waits.length === 0) break;
1381
- await settle(Promise.all(waits), signal);
1382
- }
1383
- const event = new RequestEvent(name, {
1384
- detail,
1385
- signal,
1386
- bubbles: true,
1387
- composed: true
1388
- });
1389
- const recorded = port(event);
1390
- recorded.p = progress;
1391
- target.dispatchEvent(event);
1392
- answered?.(recorded.a !== void 0);
1393
- try {
1394
- return recorded.a === void 0 ? null : await settle(recorded.a[0], signal);
1395
- } finally {
1396
- recorded.p = void 0;
1397
- }
1398
- }
1399
- //#endregion
1400
1229
  //#region src/editor/requests.ts
1401
1230
  /** Answers a request event with a responder function, as an HTML Next framework function prop does. */
1402
1231
  function respondWithFunction(event, responder) {
@@ -3345,45 +3174,143 @@ var expandNodeView = ({ editor, node, getPos }) => {
3345
3174
  };
3346
3175
  };
3347
3176
  //#endregion
3348
- //#region src/vue/editor/divider.ts
3349
- /** A pointer-sized document divider using the shared separator's selected state.
3350
- * @lifecycle The editor owns the node and deletion; destroying the view releases
3351
- * its Vue app and editability subscription.
3177
+ //#region vanilla/UiSeparator.js
3178
+ var S = {
3179
+ n: [],
3180
+ t: [],
3181
+ f: import.meta.url,
3182
+ g: "ui-separator"
3183
+ };
3184
+ var D = {
3185
+ g: "ui-separator",
3186
+ props: {
3187
+ "emphasis": {
3188
+ "required": false,
3189
+ "values": ["normal", "strong"],
3190
+ "default": "normal",
3191
+ c: checkFormat("keyword", keywordFormat),
3192
+ t: "keyword",
3193
+ j: 0,
3194
+ m: [
3195
+ "p",
3196
+ formatOf(keywordFormat),
3197
+ false
3198
+ ]
3199
+ },
3200
+ "orientation": {
3201
+ "required": false,
3202
+ "values": ["horizontal", "vertical"],
3203
+ "default": "horizontal",
3204
+ c: checkFormat("keyword", keywordFormat),
3205
+ t: "keyword",
3206
+ j: 0,
3207
+ m: [
3208
+ "p",
3209
+ formatOf(keywordFormat),
3210
+ false
3211
+ ]
3212
+ },
3213
+ "selectable": {
3214
+ "required": false,
3215
+ "default": false,
3216
+ c: checkBoolean,
3217
+ t: "boolean",
3218
+ j: 0,
3219
+ m: "b"
3220
+ },
3221
+ "selected": {
3222
+ "required": false,
3223
+ "default": false,
3224
+ c: checkBoolean,
3225
+ t: "boolean",
3226
+ j: 0,
3227
+ m: "b"
3228
+ }
3229
+ }
3230
+ };
3231
+ function createUiSeparator(options = {}, html) {
3232
+ const { attributes = {}, children = [], slots = {}, ...componentProps } = options;
3233
+ const element = document.createElement("hr");
3234
+ for (const [name, value] of Object.entries(attributes)) {
3235
+ if (value === null || value === void 0 || value === false) continue;
3236
+ element.setAttribute(name, value === true ? "" : String(value));
3237
+ }
3238
+ element.setAttribute("data-component", "ui-separator");
3239
+ const v = [
3240
+ null,
3241
+ null,
3242
+ null,
3243
+ null
3244
+ ];
3245
+ const B = acceptProps(element, D, S.n, v, componentProps, [], html);
3246
+ const p0 = (r, c, d) => {
3247
+ if (c & 1073741826) {
3248
+ const x0 = v[1];
3249
+ const s0 = toAttribute(x0, "aria-orientation");
3250
+ if (s0 !== r.v0) writeAttribute(r.n, "aria-orientation", r.v0 = s0);
3251
+ }
3252
+ if (c & 13) {
3253
+ const s1 = hostState([
3254
+ "emphasis",
3255
+ "selectable",
3256
+ "selected"
3257
+ ], [
3258
+ v[0],
3259
+ v[2],
3260
+ v[3]
3261
+ ]);
3262
+ if (s1 !== r.v1) writeAttribute(r.n, "data-ui-separator-state", (r.v1 = s1) === "" ? null : s1);
3263
+ }
3264
+ };
3265
+ const R = {
3266
+ n: element,
3267
+ v0: void 0,
3268
+ v1: void 0
3269
+ };
3270
+ const I = { B };
3271
+ attachGeneratedController(element, S, v, ((c, d) => p0(R, c, d)), void 0, void 0, I);
3272
+ manageProps(I);
3273
+ return element;
3274
+ }
3275
+ //#endregion
3276
+ //#region src/editor/node-views/divider.ts
3277
+ /**
3278
+ * A pointer-sized document divider drawn by Separator, selectable while the editor is editable.
3279
+ * @lifecycle The editor owns the node and its deletion. Separator connects while ProseMirror keeps its
3280
+ * element in the document and disconnects once ProseMirror removes it; destroying the view stops
3281
+ * following the editor's editability.
3352
3282
  */
3353
3283
  var dividerNodeView = ({ editor, getPos }) => {
3354
- const mount = document.createElement("div");
3355
- const selected = ref(false);
3356
- const editable = ref(editor.isEditable);
3357
- const refresh = () => {
3358
- editable.value = editor.isEditable;
3359
- };
3360
- const app = createApp({ render: () => h(Separator, {
3361
- selectable: editable.value,
3362
- selected: editable.value && selected.value,
3363
- contenteditable: "false",
3364
- onClick: () => {
3365
- const position = getPos();
3366
- if (!editor.isEditable || typeof position !== "number") return;
3367
- editor.view.dispatch(editor.state.tr.setSelection(NodeSelection.create(editor.state.doc, position)));
3368
- editor.view.focus();
3369
- }
3370
- }) });
3371
- app.mount(mount);
3372
- const dom = mount.firstElementChild;
3373
- editor.on("update", refresh);
3284
+ let selected = false;
3285
+ const dom = createUiSeparator({
3286
+ selectable: editor.isEditable,
3287
+ attributes: { contenteditable: "false" }
3288
+ });
3289
+ const render = () => updateComponentProps(dom, {
3290
+ selectable: editor.isEditable,
3291
+ selected: editor.isEditable && selected
3292
+ });
3293
+ dom.addEventListener("click", () => {
3294
+ const position = getPos();
3295
+ if (!editor.isEditable || typeof position !== "number") return;
3296
+ editor.view.dispatch(editor.state.tr.setSelection(NodeSelection.create(editor.state.doc, position)));
3297
+ editor.view.focus();
3298
+ });
3299
+ editor.on("update", render);
3374
3300
  return {
3375
3301
  dom,
3376
3302
  selectNode() {
3377
- selected.value = true;
3303
+ selected = true;
3304
+ render();
3378
3305
  },
3379
3306
  deselectNode() {
3380
- selected.value = false;
3307
+ selected = false;
3308
+ render();
3381
3309
  },
3382
3310
  stopEvent: () => false,
3383
3311
  ignoreMutation: (mutation) => mutation.type !== "selection",
3384
3312
  destroy() {
3385
- editor.off("update", refresh);
3386
- app.unmount();
3313
+ editor.off("update", render);
3387
3314
  }
3388
3315
  };
3389
3316
  };
@@ -1,6 +0,0 @@
1
- import type { NodeViewRenderer } from "@tiptap/core";
2
- /** A pointer-sized document divider using the shared separator's selected state.
3
- * @lifecycle The editor owns the node and deletion; destroying the view releases
4
- * its Vue app and editability subscription.
5
- */
6
- export declare const dividerNodeView: NodeViewRenderer;