@threadlabs/looma 0.23.21 → 0.23.23

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 (54) hide show
  1. package/components/shared/overlay.js +1 -1
  2. package/components/ui-button/ui-button.html +68 -2
  3. package/components/ui-image/ui-image.html +177 -0
  4. package/components/ui-image/ui-image.js +107 -0
  5. package/components/ui-menu/ui-menu.js +1 -1
  6. package/components/ui-popover/ui-popover.js +4 -1
  7. package/components/ui-separator/ui-separator.html +26 -2
  8. package/dist/index.js +24 -23
  9. package/editor/chunks/extensions.js +633 -20
  10. package/editor/extensions/block-selection.d.ts +6 -0
  11. package/editor/extensions/expand.d.ts +31 -0
  12. package/editor/extensions/image.d.ts +12 -0
  13. package/editor/extensions/index.d.ts +4 -1
  14. package/editor/extensions/index.js +2 -2
  15. package/editor/extensions/preset.d.ts +8 -0
  16. package/editor/extensions/slash-command.d.ts +31 -3
  17. package/editor/extensions/table-of-contents.d.ts +58 -0
  18. package/editor/index.js +2 -2
  19. package/package.json +1 -1
  20. package/styles/ui-button.css +39 -0
  21. package/styles/ui-image.css +153 -0
  22. package/styles/ui-separator.css +22 -2
  23. package/vanilla/UiButton.d.ts +1 -1
  24. package/vanilla/UiButton.js +126 -10
  25. package/vanilla/UiImage.d.ts +21 -0
  26. package/vanilla/UiImage.js +84 -0
  27. package/vanilla/UiSeparator.d.ts +3 -0
  28. package/vanilla/UiSeparator.js +14 -4
  29. package/vanilla/index.js +1 -0
  30. package/vue/UiButton.d.ts +18 -6
  31. package/vue/UiButton.vue +269 -8
  32. package/vue/UiImage.d.ts +100 -0
  33. package/vue/UiImage.js +2 -0
  34. package/vue/UiImage.vue +463 -0
  35. package/vue/UiListItem.d.ts +1 -1
  36. package/vue/UiSection.d.ts +1 -1
  37. package/vue/UiSeparator.d.ts +27 -0
  38. package/vue/UiSeparator.vue +94 -3
  39. package/vue/UiSidebar.d.ts +1 -1
  40. package/vue/chunks/UiButton.js +196 -12
  41. package/vue/chunks/UiImage.js +313 -0
  42. package/vue/chunks/UiSeparator.js +106 -22
  43. package/vue/components.css +252 -53
  44. package/vue/editor/LoomaEditor.d.ts +19 -2
  45. package/vue/editor/divider.d.ts +6 -0
  46. package/vue/editor/expand.d.ts +7 -0
  47. package/vue/editor/image-controls.d.ts +19 -0
  48. package/vue/editor/image-delivery.d.ts +2 -2
  49. package/vue/editor/image.d.ts +7 -0
  50. package/vue/editor/index.d.ts +1 -1
  51. package/vue/editor/index.js +1188 -52
  52. package/vue/editor/table-of-contents.d.ts +7 -0
  53. package/vue/index.d.ts +1 -0
  54. package/vue/index.js +2 -1
@@ -824,7 +824,7 @@ svg[data-v-1012d0f2],
824
824
  syntax: "*";
825
825
  inherits: false;
826
826
  }
827
- [data-component~="ui-button"][data-v-07b59e63] {
827
+ [data-component~="ui-button"][data-v-e0bdc48b] {
828
828
  --_hue: var(--ui-accent);
829
829
  --_hue-hover: var(--ui-accent-hover);
830
830
  --_hue-active: var(--ui-accent-active);
@@ -843,15 +843,15 @@ svg[data-v-1012d0f2],
843
843
  vertical-align: middle;
844
844
  color: var(--_tone-text);
845
845
  }
846
- [data-component~="ui-button"][data-ui-button-state~="align=start"][data-v-07b59e63] {
846
+ [data-component~="ui-button"][data-ui-button-state~="align=start"][data-v-e0bdc48b] {
847
847
  justify-content: flex-start;
848
848
  text-align: start;
849
849
  }
850
- [data-component~="ui-button"][data-ui-button-state~="stretch"][data-v-07b59e63] {
850
+ [data-component~="ui-button"][data-ui-button-state~="stretch"][data-v-e0bdc48b] {
851
851
  display: flex;
852
852
  inline-size: 100%;
853
853
  }
854
- [data-component~="ui-button"][data-v-07b59e63] {
854
+ [data-component~="ui-button"][data-v-e0bdc48b] {
855
855
  appearance: none;
856
856
  min-block-size: var(--ui-button-min-block-size, var(--ui-control-size-md));
857
857
  border-style: solid;
@@ -879,65 +879,101 @@ svg[data-v-1012d0f2],
879
879
  }
880
880
 
881
881
  /* An anchor keeps the boxed button look even when an app styles ordinary links. */
882
- [data-component~="ui-button"][data-v-07b59e63]:is(a) {
882
+ [data-component~="ui-button"][data-v-e0bdc48b]:is(a) {
883
883
  text-decoration: none;
884
884
  }
885
- .spinner-wrap[data-v-07b59e63] {
885
+ .spinner-wrap[data-v-e0bdc48b] {
886
886
  display: inline-flex;
887
887
  flex: none;
888
888
  margin-inline-end: var(--ui-space-2);
889
889
  vertical-align: middle;
890
890
  }
891
891
  @supports (text-box-trim: trim-both) {
892
- [data-component~="ui-button"][data-v-07b59e63] {
892
+ [data-component~="ui-button"][data-v-e0bdc48b] {
893
893
  text-box-trim: trim-both;
894
894
  text-box-edge: cap alphabetic;
895
895
  }
896
896
  }
897
- [data-component~="ui-button"][data-ui-button-state~="size=xs"][data-v-07b59e63] {
897
+ [data-component~="ui-button"][data-ui-button-state~="size=xs"][data-v-e0bdc48b] {
898
898
  min-block-size: 1.5rem;
899
899
  padding: calc(var(--ui-space-0-5) * 1.5) calc(var(--ui-space-1) * 1.25);
900
900
  font-size: var(--ui-font-size-xs);
901
901
  }
902
- [data-component~="ui-button"][data-ui-button-state~="shape=pill"][data-v-07b59e63] {
902
+ [data-component~="ui-button"][data-ui-button-state~="shape=pill"][data-v-e0bdc48b] {
903
903
  border-radius: var(--ui-button-radius, var(--ui-radius-round));
904
904
  }
905
- [data-component~="ui-button"][data-ui-button-state~="size=sm"][data-v-07b59e63] {
905
+ [data-component~="ui-button"][data-ui-button-state~="size=sm"][data-v-e0bdc48b] {
906
906
  min-block-size: var(--ui-control-size-sm);
907
907
  padding: var(--ui-space-1) var(--ui-space-2);
908
908
  font-size: var(--ui-font-size-sm);
909
909
  }
910
- [data-component~="ui-button"][data-ui-button-state~="size=lg"][data-v-07b59e63] {
910
+ [data-component~="ui-button"][data-ui-button-state~="size=lg"][data-v-e0bdc48b] {
911
911
  min-block-size: var(--ui-control-size-lg);
912
912
  padding: var(--ui-space-3) var(--ui-space-4);
913
913
  font-size: var(--ui-font-size-md);
914
914
  }
915
915
 
916
+ /* Card actions inherit Button's palette and behavior; only their shared layout changes.
917
+ The icon may contain any decorative composition. Empty icons remove their column. */
918
+ [data-component~="ui-button"][data-ui-button-state~="variant=card"][data-v-e0bdc48b] {
919
+ display: grid;
920
+ grid-template-columns: auto minmax(0, 1fr) auto;
921
+ align-items: start;
922
+ gap: var(--ui-space-3);
923
+ inline-size: 100%;
924
+ min-block-size: max(var(--ui-control-min-block-size), var(--ui-control-size-md));
925
+ text-align: start;
926
+ white-space: normal;
927
+ box-shadow: none;
928
+ background-image: none;
929
+ }
930
+ [data-component~="ui-button"][data-ui-button-state~="variant=card"][data-ui-button-state~="size=lg"][data-v-e0bdc48b] {
931
+ min-block-size: max(var(--ui-control-min-block-size), var(--ui-control-size-lg));
932
+ }
933
+ [data-component~="ui-button"][data-ui-button-state~="variant=card"][data-v-e0bdc48b]:has(.card-icon:empty) {
934
+ grid-template-columns: minmax(0, 1fr) auto;
935
+ }
936
+ .card-icon[data-v-e0bdc48b] {
937
+ display: flex;
938
+ align-items: flex-start;
939
+ }
940
+ .card-icon[data-v-e0bdc48b]:empty {
941
+ display: none;
942
+ }
943
+ .card-content[data-v-e0bdc48b] {
944
+ min-inline-size: 0;
945
+ }
946
+ .card-action[data-v-e0bdc48b] {
947
+ display: flex;
948
+ align-items: center;
949
+ align-self: center;
950
+ }
951
+
916
952
  /* A link is never :enabled, so interactive states test "not unavailable" instead: for a button that
917
953
  is exactly :enabled, at the same specificity, and it also matches a link that can be followed. */
918
- [data-component~="ui-button"][data-v-07b59e63]:hover:not(:disabled, [aria-disabled="true"]) {
954
+ [data-component~="ui-button"][data-v-e0bdc48b]:hover:not(:disabled, [aria-disabled="true"]) {
919
955
  border-color: var(--ui-button-hover-border, var(--_tone-hover));
920
956
  background-color: var(--ui-button-hover-surface, oklch(from var(--_tone) l c h / 12%));
921
957
  }
922
958
 
923
959
  /* Pressed reads as pressed in: the highlight goes, and the shadow moves inside. */
924
- [data-component~="ui-button"][data-v-07b59e63]:active:not(:disabled, [aria-disabled="true"]) {
960
+ [data-component~="ui-button"][data-v-e0bdc48b]:active:not(:disabled, [aria-disabled="true"]) {
925
961
  background-color: var(--ui-button-active-surface, oklch(from var(--_tone) l c h / 18%));
926
962
  background-image: none;
927
963
  box-shadow: var(--ui-pressed);
928
964
  }
929
- [data-component~="ui-button"][data-ui-button-state~="variant=solid"][data-v-07b59e63],
930
- [data-component~="ui-button"][data-ui-button-state~="variant=danger"][data-v-07b59e63] {
965
+ [data-component~="ui-button"][data-ui-button-state~="variant=solid"][data-v-e0bdc48b],
966
+ [data-component~="ui-button"][data-ui-button-state~="variant=danger"][data-v-e0bdc48b] {
931
967
  border-color: var(--_tone);
932
968
  background-color: var(--_tone);
933
969
  color: var(--_on-tone);
934
970
  box-shadow: var(--ui-button-solid-shadow, var(--ui-raised));
935
971
  }
936
- :is([data-component~="ui-button"][data-ui-button-state~="variant=solid"][data-v-07b59e63], [data-component~="ui-button"][data-ui-button-state~="variant=danger"][data-v-07b59e63]):hover:not(:disabled, [aria-disabled="true"]) {
972
+ :is([data-component~="ui-button"][data-ui-button-state~="variant=solid"][data-v-e0bdc48b], [data-component~="ui-button"][data-ui-button-state~="variant=danger"][data-v-e0bdc48b]):hover:not(:disabled, [aria-disabled="true"]) {
937
973
  background-color: var(--_tone-hover);
938
974
  border-color: var(--_tone-hover);
939
975
  }
940
- :is([data-component~="ui-button"][data-ui-button-state~="variant=solid"][data-v-07b59e63], [data-component~="ui-button"][data-ui-button-state~="variant=danger"][data-v-07b59e63]):active:not(:disabled, [aria-disabled="true"]) {
976
+ :is([data-component~="ui-button"][data-ui-button-state~="variant=solid"][data-v-e0bdc48b], [data-component~="ui-button"][data-ui-button-state~="variant=danger"][data-v-e0bdc48b]):active:not(:disabled, [aria-disabled="true"]) {
941
977
  background-color: var(--_tone-active);
942
978
  border-color: var(--_tone-active);
943
979
  background-image: none;
@@ -946,43 +982,43 @@ svg[data-v-1012d0f2],
946
982
 
947
983
  /* Tone is the colour, variant is the volume. Every pairing works: a danger outline is a
948
984
  destructive secondary action, not a special case. */
949
- [data-component~="ui-button"][data-ui-button-state~="tone=neutral"][data-v-07b59e63] {
985
+ [data-component~="ui-button"][data-ui-button-state~="tone=neutral"][data-v-e0bdc48b] {
950
986
  --_hue: var(--ui-text);
951
987
  --_hue-hover: var(--ui-text);
952
988
  --_hue-active: var(--ui-text);
953
989
  --_hue-text: var(--ui-text);
954
990
  --_on-hue: var(--ui-surface);
955
991
  }
956
- [data-component~="ui-button"][data-ui-button-state~="tone=danger"][data-v-07b59e63],
957
- [data-component~="ui-button"][data-ui-button-state~="variant=danger"][data-v-07b59e63] {
992
+ [data-component~="ui-button"][data-ui-button-state~="tone=danger"][data-v-e0bdc48b],
993
+ [data-component~="ui-button"][data-ui-button-state~="variant=danger"][data-v-e0bdc48b] {
958
994
  --_hue: var(--ui-danger);
959
995
  --_hue-hover: var(--ui-danger-hover);
960
996
  --_hue-active: var(--ui-danger-hover);
961
997
  --_hue-text: var(--ui-danger-hover);
962
998
  --_on-hue: var(--ui-on-danger);
963
999
  }
964
- [data-component~="ui-button"][data-ui-button-state~="tone=success"][data-v-07b59e63] {
1000
+ [data-component~="ui-button"][data-ui-button-state~="tone=success"][data-v-e0bdc48b] {
965
1001
  --_hue: var(--ui-success);
966
1002
  --_hue-hover: color-mix(in srgb, var(--ui-success) 85%, #000);
967
1003
  --_hue-active: color-mix(in srgb, var(--ui-success) 75%, #000);
968
1004
  --_hue-text: color-mix(in srgb, var(--ui-success) 85%, #000);
969
1005
  --_on-hue: var(--ui-surface);
970
1006
  }
971
- [data-component~="ui-button"][data-ui-button-state~="tone=warning"][data-v-07b59e63] {
1007
+ [data-component~="ui-button"][data-ui-button-state~="tone=warning"][data-v-e0bdc48b] {
972
1008
  --_hue: var(--ui-warning);
973
1009
  --_hue-hover: color-mix(in srgb, var(--ui-warning) 85%, #000);
974
1010
  --_hue-active: color-mix(in srgb, var(--ui-warning) 75%, #000);
975
1011
  --_hue-text: color-mix(in srgb, var(--ui-warning) 85%, #000);
976
1012
  --_on-hue: var(--ui-surface);
977
1013
  }
978
- [data-component~="ui-button"][data-ui-button-state~="tone=info"][data-v-07b59e63] {
1014
+ [data-component~="ui-button"][data-ui-button-state~="tone=info"][data-v-e0bdc48b] {
979
1015
  --_hue: var(--ui-info);
980
1016
  --_hue-hover: color-mix(in srgb, var(--ui-info) 85%, #000);
981
1017
  --_hue-active: color-mix(in srgb, var(--ui-info) 75%, #000);
982
1018
  --_hue-text: color-mix(in srgb, var(--ui-info) 85%, #000);
983
1019
  --_on-hue: var(--ui-surface);
984
1020
  }
985
- [data-component~="ui-button"][data-ui-button-state~="variant=ghost"][data-v-07b59e63] {
1021
+ [data-component~="ui-button"][data-ui-button-state~="variant=ghost"][data-v-e0bdc48b] {
986
1022
  border-color: transparent;
987
1023
  background: transparent;
988
1024
  box-shadow: none;
@@ -990,14 +1026,14 @@ svg[data-v-1012d0f2],
990
1026
  }
991
1027
 
992
1028
  /* Tints derive from the text colour, not a surface token, so hover and press show on any background. */
993
- [data-component~="ui-button"][data-ui-button-state~="variant=ghost"][data-v-07b59e63]:hover:not(:disabled, [aria-disabled="true"]) {
1029
+ [data-component~="ui-button"][data-ui-button-state~="variant=ghost"][data-v-e0bdc48b]:hover:not(:disabled, [aria-disabled="true"]) {
994
1030
  background-color: var(--ui-button-ghost-hover-surface, oklch(from var(--_tone) l c h / 10%));
995
1031
  border-color: transparent;
996
1032
  }
997
1033
 
998
1034
  /* Pressed, or holding its menu open, a ghost button sinks slightly: a light surface with a faint
999
1035
  edge and a soft inner shadow. Lighter than hover, so an open menu's trigger never reads as heavy. */
1000
- [data-component~="ui-button"][data-ui-button-state~="variant=ghost"][data-v-07b59e63]:is(:active, [aria-expanded="true"]):not(:disabled, [aria-disabled="true"]) {
1036
+ [data-component~="ui-button"][data-ui-button-state~="variant=ghost"][data-v-e0bdc48b]:is(:active, [aria-expanded="true"]):not(:disabled, [aria-disabled="true"]) {
1001
1037
  background-color: var(
1002
1038
  --ui-button-ghost-active-surface,
1003
1039
  color-mix(in oklch, var(--ui-surface), var(--_tone) 3%)
@@ -1009,13 +1045,13 @@ svg[data-v-1012d0f2],
1009
1045
  }
1010
1046
 
1011
1047
  /* A soft halo that transitions in with the other box-shadow changes, layered on the raised shadow. */
1012
- [data-component~="ui-button"][data-v-07b59e63]:focus-visible {
1048
+ [data-component~="ui-button"][data-v-e0bdc48b]:focus-visible {
1013
1049
  outline: none;
1014
1050
  box-shadow:
1015
1051
  var(--ui-button-shadow, var(--ui-raised)),
1016
1052
  var(--ui-focus-halo);
1017
1053
  }
1018
- [data-component~="ui-button"][data-ui-button-state~="variant=solid"][data-v-07b59e63]:focus-visible {
1054
+ [data-component~="ui-button"][data-ui-button-state~="variant=solid"][data-v-e0bdc48b]:focus-visible {
1019
1055
  box-shadow:
1020
1056
  var(--ui-button-solid-shadow, var(--ui-raised)),
1021
1057
  var(--ui-focus-halo);
@@ -1023,7 +1059,7 @@ svg[data-v-1012d0f2],
1023
1059
 
1024
1060
  /* A loading action stays visually active; only an explicitly disabled action takes the
1025
1061
  neutral disabled palette. Its flat surface and muted ink make the state unambiguous. */
1026
- [data-component~="ui-button"][data-v-07b59e63]:is(:disabled, [aria-disabled="true"]:not([aria-busy="true"])) {
1062
+ [data-component~="ui-button"][data-v-e0bdc48b]:is(:disabled, [aria-disabled="true"]:not([aria-busy="true"])) {
1027
1063
  border-color: var(--ui-disabled-border);
1028
1064
  background-color: var(--ui-disabled-surface);
1029
1065
  color: var(--ui-disabled-text);
@@ -1033,19 +1069,19 @@ svg[data-v-1012d0f2],
1033
1069
  background-image: none;
1034
1070
  box-shadow: none;
1035
1071
  }
1036
- :is([data-component~="ui-button"][data-ui-button-state~="loading"][data-v-07b59e63], [data-component~="ui-button"][data-ui-button-state~="pending"][data-v-07b59e63]):focus-visible {
1072
+ :is([data-component~="ui-button"][data-ui-button-state~="loading"][data-v-e0bdc48b], [data-component~="ui-button"][data-ui-button-state~="pending"][data-v-e0bdc48b]):focus-visible {
1037
1073
  box-shadow: var(--ui-focus-halo);
1038
1074
  }
1039
1075
 
1040
1076
  /* Ghost keeps no border, but gains the same neutral surface as other disabled buttons. */
1041
- [data-component~="ui-button"][data-ui-button-state~="variant=ghost"][data-v-07b59e63]:is(:disabled, [aria-disabled="true"]:not([aria-busy="true"])) {
1077
+ [data-component~="ui-button"][data-ui-button-state~="variant=ghost"][data-v-e0bdc48b]:is(:disabled, [aria-disabled="true"]:not([aria-busy="true"])) {
1042
1078
  background-color: var(--ui-disabled-surface);
1043
1079
  border-color: transparent;
1044
1080
  }
1045
1081
 
1046
1082
  /* The size states outrank :host, so sm is named too, or a small button would stay small. */
1047
1083
  @media (pointer: coarse) {
1048
- [data-component~="ui-button"][data-v-07b59e63], [data-component~="ui-button"][data-ui-button-state~="size=sm"][data-v-07b59e63] {
1084
+ [data-component~="ui-button"][data-v-e0bdc48b], [data-component~="ui-button"][data-ui-button-state~="size=sm"][data-v-e0bdc48b] {
1049
1085
  min-block-size: var(--ui-control-min-block-size);
1050
1086
  }
1051
1087
  }
@@ -1056,15 +1092,15 @@ svg[data-v-1012d0f2],
1056
1092
  area. xs sits inside a line of text the same way, so it keeps its size and gains the area too.
1057
1093
  Only these need it: a hit area on every button would reach over its neighbours. */
1058
1094
  html[data-ui-input-modality="touch"]
1059
- [data-component~="ui-button"][data-ui-button-state~="variant=link"][data-v-07b59e63],
1095
+ [data-component~="ui-button"][data-ui-button-state~="variant=link"][data-v-e0bdc48b],
1060
1096
  html[data-ui-input-modality="touch"]
1061
- [data-component~="ui-button"][data-ui-button-state~="size=xs"][data-v-07b59e63] {
1097
+ [data-component~="ui-button"][data-ui-button-state~="size=xs"][data-v-e0bdc48b] {
1062
1098
  position: relative;
1063
1099
  }
1064
1100
  html[data-ui-input-modality="touch"]
1065
- [data-component~="ui-button"][data-ui-button-state~="variant=link"][data-v-07b59e63]::after,
1101
+ [data-component~="ui-button"][data-ui-button-state~="variant=link"][data-v-e0bdc48b]::after,
1066
1102
  html[data-ui-input-modality="touch"]
1067
- [data-component~="ui-button"][data-ui-button-state~="size=xs"][data-v-07b59e63]::after {
1103
+ [data-component~="ui-button"][data-ui-button-state~="size=xs"][data-v-e0bdc48b]::after {
1068
1104
  position: absolute;
1069
1105
  inset: 50% auto auto 50%;
1070
1106
  inline-size: max(100%, var(--ui-control-min-block-size));
@@ -1075,7 +1111,7 @@ html[data-ui-input-modality="touch"]
1075
1111
 
1076
1112
  /* Link: an inline text action. No box, padding, or minimum height (inline targets are exempt under
1077
1113
  WCAG 2.5.8); the inherited font; an underline that strengthens on hover; a visible focus ring. */
1078
- [data-component~="ui-button"][data-ui-button-state~="variant=link"][data-v-07b59e63] {
1114
+ [data-component~="ui-button"][data-ui-button-state~="variant=link"][data-v-e0bdc48b] {
1079
1115
  display: inline;
1080
1116
  min-block-size: 0;
1081
1117
  padding: 0;
@@ -1093,21 +1129,21 @@ html[data-ui-input-modality="touch"]
1093
1129
  color var(--ui-motion-fast) var(--ui-motion-ease),
1094
1130
  text-decoration-color var(--ui-motion-fast) var(--ui-motion-ease);
1095
1131
  }
1096
- [data-component~="ui-button"][data-ui-button-state~="variant=link"][data-v-07b59e63]:hover:not(:disabled, [aria-disabled="true"]) {
1132
+ [data-component~="ui-button"][data-ui-button-state~="variant=link"][data-v-e0bdc48b]:hover:not(:disabled, [aria-disabled="true"]) {
1097
1133
  color: var(--ui-button-link-hover-text, var(--ui-text-primary));
1098
1134
  text-decoration-color: currentColor;
1099
1135
  background: none;
1100
1136
  transform: none;
1101
1137
  }
1102
- [data-component~="ui-button"][data-ui-button-state~="variant=link"][data-v-07b59e63]:active:not(:disabled, [aria-disabled="true"]) {
1138
+ [data-component~="ui-button"][data-ui-button-state~="variant=link"][data-v-e0bdc48b]:active:not(:disabled, [aria-disabled="true"]) {
1103
1139
  background: none;
1104
1140
  transform: none;
1105
1141
  }
1106
- [data-component~="ui-button"][data-ui-button-state~="variant=link"][data-v-07b59e63]:is(:disabled, [aria-disabled="true"]:not([aria-busy="true"])) {
1142
+ [data-component~="ui-button"][data-ui-button-state~="variant=link"][data-v-e0bdc48b]:is(:disabled, [aria-disabled="true"]:not([aria-busy="true"])) {
1107
1143
  background: none;
1108
1144
  border-color: transparent;
1109
1145
  }
1110
- [data-component~="ui-button"][data-ui-button-state~="variant=link"][data-v-07b59e63]:focus-visible {
1146
+ [data-component~="ui-button"][data-ui-button-state~="variant=link"][data-v-e0bdc48b]:focus-visible {
1111
1147
  box-shadow: none;
1112
1148
  /* Fallback keeps the ring if the token is missing: an unresolved var() would void the outline. */
1113
1149
  outline: var(--ui-focus-width) solid var(--ui-focus-ring);
@@ -1115,7 +1151,7 @@ html[data-ui-input-modality="touch"]
1115
1151
  }
1116
1152
 
1117
1153
  /* A component that sets its display still honours the hidden attribute on its root. */
1118
- [data-component~="ui-button"][hidden][data-v-07b59e63] {
1154
+ [data-component~="ui-button"][hidden][data-v-e0bdc48b] {
1119
1155
  display: none;
1120
1156
  }
1121
1157
 
@@ -4789,6 +4825,156 @@ svg[data-v-539c5c85-s] {
4789
4825
  display: none;
4790
4826
  }
4791
4827
 
4828
+ [data-component~="ui-image"][data-v-2895fbac] {
4829
+ display: flex;
4830
+ justify-content: flex-start;
4831
+ box-sizing: border-box;
4832
+ max-inline-size: 100%;
4833
+ min-inline-size: 0;
4834
+ margin: 0;
4835
+ padding: var(--ui-space-3) 0;
4836
+ }
4837
+ [data-component~="ui-image"][data-ui-image-state~="placement=center"][data-v-2895fbac] {
4838
+ justify-content: center;
4839
+ }
4840
+ [data-component~="ui-image"][data-ui-image-state~="placement=wrap-left"][data-v-2895fbac] {
4841
+ float: left;
4842
+ padding-inline-end: var(--ui-space-4);
4843
+ }
4844
+ [data-component~="ui-image"][data-ui-image-state~="placement=wrap-right"][data-v-2895fbac] {
4845
+ float: right;
4846
+ padding-inline-start: var(--ui-space-4);
4847
+ }
4848
+ [data-component~="ui-image"][data-v-2895fbac]:is([data-component~="ui-image"][data-ui-image-state~="placement=wrap-left"], [data-component~="ui-image"][data-ui-image-state~="placement=wrap-right"]) {
4849
+ inline-size: calc(var(--_ui-image-width, calc(100% / 3)) + var(--ui-space-4));
4850
+ }
4851
+ [data-component~="ui-image"]:is([data-component~="ui-image"][data-ui-image-state~="placement=wrap-left"], [data-component~="ui-image"][data-ui-image-state~="placement=wrap-right"])
4852
+ .frame[data-v-2895fbac] {
4853
+ inline-size: 100%;
4854
+ }
4855
+ [data-component~="ui-image"]:is([data-component~="ui-image"][data-ui-image-state~="placement=wrap-left"], [data-component~="ui-image"][data-ui-image-state~="placement=wrap-right"])
4856
+ .frame
4857
+ img[data-v-2895fbac],
4858
+ [data-component~="ui-image"]:is([data-component~="ui-image"][data-ui-image-state~="placement=wrap-left"], [data-component~="ui-image"][data-ui-image-state~="placement=wrap-right"])
4859
+ .frame
4860
+ img[data-v-2895fbac-s] {
4861
+ inline-size: 100%;
4862
+ }
4863
+ .frame[data-v-2895fbac] {
4864
+ position: relative;
4865
+ flex: 0 1 auto;
4866
+ inline-size: var(--_ui-image-width, fit-content);
4867
+ max-inline-size: 100%;
4868
+ min-inline-size: 0;
4869
+ align-self: flex-start;
4870
+ border-radius: var(--ui-radius-md);
4871
+ }
4872
+ .frame img[data-v-2895fbac], .frame img[data-v-2895fbac-s] {
4873
+ display: block;
4874
+ max-inline-size: 100%;
4875
+ block-size: auto;
4876
+ margin: 0;
4877
+ border-radius: inherit;
4878
+ }
4879
+ [data-component~="ui-image"][data-resizing] .frame img[data-v-2895fbac],
4880
+ [data-component~="ui-image"][data-resizing] .frame img[data-v-2895fbac-s] {
4881
+ inline-size: 100%;
4882
+ }
4883
+ [data-component~="ui-image"][data-ui-image-state~="selected"] .frame[data-v-2895fbac] {
4884
+ outline: var(--ui-focus-width) solid var(--ui-focus-ring);
4885
+ outline-offset: var(--ui-border-width);
4886
+ }
4887
+ .handle[data-v-2895fbac] {
4888
+ position: absolute;
4889
+ z-index: 1;
4890
+ inline-size: 24px;
4891
+ block-size: 24px;
4892
+ padding: 0;
4893
+ border: 0;
4894
+ background: transparent;
4895
+ touch-action: none;
4896
+ }
4897
+ .handle[data-v-2895fbac]::after {
4898
+ content: "";
4899
+ position: absolute;
4900
+ inset: 8px;
4901
+ background: var(--ui-surface);
4902
+ border: var(--ui-border-width) solid var(--ui-focus-ring);
4903
+ border-radius: var(--ui-radius-sm);
4904
+ }
4905
+ .handle[data-v-2895fbac]:focus-visible {
4906
+ outline: var(--ui-focus-width) solid var(--ui-focus-ring);
4907
+ border-radius: var(--ui-radius-sm);
4908
+ }
4909
+ .handle[data-corner="top-left"][data-v-2895fbac] {
4910
+ top: -12px;
4911
+ left: -12px;
4912
+ cursor: nwse-resize;
4913
+ }
4914
+ .handle[data-corner="top-right"][data-v-2895fbac] {
4915
+ top: -12px;
4916
+ right: -12px;
4917
+ cursor: nesw-resize;
4918
+ }
4919
+ .handle[data-corner="bottom-left"][data-v-2895fbac] {
4920
+ bottom: -12px;
4921
+ left: -12px;
4922
+ cursor: nesw-resize;
4923
+ }
4924
+ .handle[data-corner="bottom-right"][data-v-2895fbac] {
4925
+ bottom: -12px;
4926
+ right: -12px;
4927
+ cursor: nwse-resize;
4928
+ }
4929
+ .handles[hidden][data-v-2895fbac], [data-component~="ui-image"][hidden][data-v-2895fbac] {
4930
+ display: none;
4931
+ }
4932
+ @media (max-width: 767px) {
4933
+ [data-component~="ui-image"][data-v-2895fbac]:is([data-component~="ui-image"][data-ui-image-state~="placement=wrap-left"], [data-component~="ui-image"][data-ui-image-state~="placement=wrap-right"]) {
4934
+ float: none;
4935
+ inline-size: 100%;
4936
+ padding-inline: 0;
4937
+ }
4938
+ [data-component~="ui-image"]:is([data-component~="ui-image"][data-ui-image-state~="placement=wrap-left"], [data-component~="ui-image"][data-ui-image-state~="placement=wrap-right"])
4939
+ .handles[data-v-2895fbac] {
4940
+ display: none;
4941
+ }
4942
+ }
4943
+ @media (pointer: coarse) {
4944
+ .handle[data-v-2895fbac] {
4945
+ inline-size: 44px;
4946
+ block-size: 44px;
4947
+ }
4948
+ .handle[data-v-2895fbac]::after {
4949
+ inset: 18px;
4950
+ }
4951
+ .handle[data-corner="top-left"][data-v-2895fbac] {
4952
+ top: -22px;
4953
+ left: -22px;
4954
+ }
4955
+ .handle[data-corner="top-right"][data-v-2895fbac] {
4956
+ top: -22px;
4957
+ right: -22px;
4958
+ }
4959
+ .handle[data-corner="bottom-left"][data-v-2895fbac] {
4960
+ bottom: -22px;
4961
+ left: -22px;
4962
+ }
4963
+ .handle[data-corner="bottom-right"][data-v-2895fbac] {
4964
+ bottom: -22px;
4965
+ right: -22px;
4966
+ }
4967
+ }
4968
+ @media (forced-colors: active) {
4969
+ [data-component~="ui-image"][data-ui-image-state~="selected"] .frame[data-v-2895fbac], .handle[data-v-2895fbac]:focus-visible {
4970
+ outline-color: Highlight;
4971
+ }
4972
+ .handle[data-v-2895fbac]::after {
4973
+ border-color: Highlight;
4974
+ background: Canvas;
4975
+ }
4976
+ }
4977
+
4792
4978
  /* This component's hooks style the element they are set on, and never a nested one. An Input
4793
4979
  Group sets --_ui-default-input-* for the input inside it, below the input's own hooks. */
4794
4980
  @property --ui-input-border {
@@ -7033,19 +7219,39 @@ dialog[data-v-d317023c] {
7033
7219
  }
7034
7220
 
7035
7221
  /* The root is the hr, so the rules name :host; a bare hr matches only a descendant. */
7036
- [data-component~="ui-separator"][data-v-5289daff] {
7222
+ [data-component~="ui-separator"][data-v-a49de10a] {
7037
7223
  box-sizing: border-box;
7038
7224
  align-self: stretch;
7039
7225
  margin: 0;
7040
7226
  border: 0;
7041
7227
  border-block-start: var(--ui-border-width) solid
7042
- var(--ui-separator-color, var(--ui-border-divider));
7228
+ var(--ui-separator-color, var(--_separator-color, var(--ui-border-divider)));
7043
7229
  }
7044
- [data-component~="ui-separator"][aria-orientation="vertical"][data-v-5289daff] {
7230
+ [data-component~="ui-separator"][data-ui-separator-state~="emphasis=strong"][data-v-a49de10a] {
7231
+ --_separator-color: var(--ui-border-strong);
7232
+ }
7233
+ [data-component~="ui-separator"][aria-orientation="vertical"][data-v-a49de10a] {
7045
7234
  block-size: auto;
7046
7235
  border-block-start: 0;
7047
7236
  border-inline-start: var(--ui-border-width) solid
7048
- var(--ui-separator-color, var(--ui-border-divider));
7237
+ var(--ui-separator-color, var(--_separator-color, var(--ui-border-divider)));
7238
+ }
7239
+ [data-component~="ui-separator"][data-ui-separator-state~="selectable"][data-v-a49de10a] {
7240
+ min-block-size: var(--ui-control-size-sm);
7241
+ border: 0;
7242
+ background: linear-gradient(
7243
+ var(--ui-separator-color, var(--ui-border-divider)),
7244
+ var(--ui-separator-color, var(--ui-border-divider))
7245
+ ) center / 100% var(--ui-border-width) no-repeat;
7246
+ cursor: pointer;
7247
+ }
7248
+ [data-component~="ui-separator"][data-ui-separator-state~="selectable"][data-v-a49de10a]:hover {
7249
+ background-color: var(--ui-surface-hover);
7250
+ }
7251
+ [data-component~="ui-separator"][data-ui-separator-state~="selected"][data-v-a49de10a] {
7252
+ outline: var(--ui-focus-width) solid var(--ui-focus-ring);
7253
+ outline-offset: var(--ui-focus-width);
7254
+ border-radius: var(--ui-radius-sm);
7049
7255
  }
7050
7256
 
7051
7257
  /* This component's hooks style the element they are set on, and never a nested one. */
@@ -9364,13 +9570,6 @@ html[data-ui-input-modality="touch"]
9364
9570
  pointer-events: none;
9365
9571
  }
9366
9572
 
9367
- .looma-editor .ProseMirror img {
9368
- max-width: 100%;
9369
- height: auto;
9370
- margin: var(--ui-space-3) 0;
9371
- border-radius: var(--ui-radius-md);
9372
- }
9373
-
9374
9573
  .looma-editor .ProseMirror img[data-looma-image]:focus-visible {
9375
9574
  outline: var(--ui-focus-width) solid var(--ui-focus-ring);
9376
9575
  outline-offset: calc(var(--ui-focus-width) + var(--ui-border-width));
@@ -1,7 +1,7 @@
1
1
  import "./looma-editor.css";
2
2
  import type { AnyExtension, Editor, JSONContent } from "@tiptap/core";
3
3
  import { type PropType, type VNode } from "vue";
4
- import { type LoomaMentionItem, type LoomaMentionProvider, type LoomaCodeLanguages } from "@threadlabs/looma/editor";
4
+ import { type LoomaSlashCommand, type LoomaMentionItem, type LoomaMentionProvider, type LoomaCodeLanguages } from "@threadlabs/looma/editor";
5
5
  import { type LoomaImageActivationDetail, type LoomaImageAttributeResolver, type LoomaImageDescriptor, type LoomaImageRenditionErrorDetail } from "./image-delivery.js";
6
6
  /**
7
7
  * Host upload result normalized into the editor's durable image descriptor.
@@ -27,6 +27,12 @@ export type LoomaImageUploader = (file: File) => Promise<string | LoomaImageUplo
27
27
  * as well as a selection; it does not alter document commands or stored content.
28
28
  */
29
29
  export type LoomaEditorToolbarMode = "bubble" | "sticky" | "popover" | "contextual";
30
+ /**
31
+ * Replace the slash inventory, or return retained defaults plus host commands.
32
+ * @ownership The host owns its command objects and mutations; Looma supplies
33
+ * fresh defaults and invokes this factory when the inventory prop changes.
34
+ */
35
+ export type LoomaEditorSlashCommands = LoomaSlashCommand[] | ((defaults: LoomaSlashCommand[]) => LoomaSlashCommand[]);
30
36
  /** A destination supplied by the host application, such as a page or record. */
31
37
  export interface LoomaLinkTarget {
32
38
  id: string;
@@ -91,6 +97,11 @@ export declare const LoomaEditor: import("vue").DefineComponent<import("vue").Ex
91
97
  type: PropType<AnyExtension[]>;
92
98
  default: () => never[];
93
99
  };
100
+ /** One inventory for the existing slash extension; an empty array intentionally supplies no commands. */
101
+ slashCommands: {
102
+ type: PropType<LoomaEditorSlashCommands>;
103
+ default: undefined;
104
+ };
94
105
  /** Override the lazy built-in catalog with application-owned code grammars. An empty object disables highlighting. */
95
106
  codeLanguages: {
96
107
  type: PropType<LoomaCodeLanguages | null>;
@@ -202,6 +213,11 @@ export declare const LoomaEditor: import("vue").DefineComponent<import("vue").Ex
202
213
  type: PropType<AnyExtension[]>;
203
214
  default: () => never[];
204
215
  };
216
+ /** One inventory for the existing slash extension; an empty array intentionally supplies no commands. */
217
+ slashCommands: {
218
+ type: PropType<LoomaEditorSlashCommands>;
219
+ default: undefined;
220
+ };
205
221
  /** Override the lazy built-in catalog with application-owned code grammars. An empty object disables highlighting. */
206
222
  codeLanguages: {
207
223
  type: PropType<LoomaCodeLanguages | null>;
@@ -271,9 +287,9 @@ export declare const LoomaEditor: import("vue").DefineComponent<import("vue").Ex
271
287
  default: boolean;
272
288
  };
273
289
  }>> & Readonly<{
290
+ onUpdate?: ((_value: JSONContent) => any) | undefined;
274
291
  "onUpdate:modelValue"?: ((_value: JSONContent) => any) | undefined;
275
292
  "onUpdate:toolbarOpen"?: ((_value: boolean) => any) | undefined;
276
- onUpdate?: ((_value: JSONContent) => any) | undefined;
277
293
  onReady?: ((_editor: Editor) => any) | undefined;
278
294
  onUploadError?: ((_error: unknown, _file: File) => any) | undefined;
279
295
  onImageActivate?: ((_detail: LoomaImageActivationDetail) => any) | undefined;
@@ -287,6 +303,7 @@ export declare const LoomaEditor: import("vue").DefineComponent<import("vue").Ex
287
303
  modelValue: JSONContent;
288
304
  editable: boolean;
289
305
  extensions: AnyExtension[];
306
+ slashCommands: LoomaEditorSlashCommands;
290
307
  mentionItems: LoomaMentionItem[];
291
308
  mentionProvider: LoomaMentionProvider | undefined;
292
309
  mentionLimit: number;
@@ -0,0 +1,6 @@
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;
@@ -0,0 +1,7 @@
1
+ import type { NodeViewRenderer } from "@tiptap/core";
2
+ /**
3
+ * Composes a section's transient disclosure and saved settings from shared controls.
4
+ * @lifecycle The content DOM remains owned by ProseMirror; Vue owns surrounding
5
+ * controls. Unmount removes the app, listeners, and editor subscription.
6
+ */
7
+ export declare const expandNodeView: NodeViewRenderer;
@@ -0,0 +1,19 @@
1
+ import type { Editor } from "@tiptap/core";
2
+ import { type PropType } from "vue";
3
+ /** One contextual editing bar; sizing belongs to the image's corner handles.
4
+ * @contract Placement applies immediately. Alt text is a secondary draft with
5
+ * Save/Cancel; editing another node cancels it rather than changing that node.
6
+ */
7
+ export declare const ImageControls: import("vue").DefineComponent<import("vue").ExtractPropTypes<{
8
+ editor: {
9
+ type: PropType<Editor>;
10
+ required: true;
11
+ };
12
+ }>, () => import("vue").VNode<import("vue").RendererNode, import("vue").RendererElement, {
13
+ [key: string]: any;
14
+ }>, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<import("vue").ExtractPropTypes<{
15
+ editor: {
16
+ type: PropType<Editor>;
17
+ required: true;
18
+ };
19
+ }>> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
@@ -1,4 +1,5 @@
1
1
  import { Extension, type Editor, type JSONContent } from "@tiptap/core";
2
+ export { normalizeImageDimension } from "../../editor/extensions/image.js";
2
3
  /** What caused an overlay to open or close. */
3
4
  type OverlayTrigger = "keyboard" | "pointer" | "programmatic";
4
5
  /**
@@ -49,8 +50,8 @@ export interface LoomaImageRenditionErrorDetail extends LoomaImageDescriptor {
49
50
  }
50
51
  interface LoomaImageDeliveryExtensionOptions {
51
52
  resolveAttributes: () => LoomaImageAttributeResolver | undefined;
53
+ isEditable: () => boolean;
52
54
  }
53
- export declare function normalizeImageDimension(value: unknown): number | undefined;
54
55
  export declare function imageDescriptorFromAttrs(attrs: Record<string, unknown>): LoomaImageDescriptor | null;
55
56
  export declare function createLoomaImageDeliveryController(options: LoomaImageDeliveryExtensionOptions): {
56
57
  extension: Extension;
@@ -58,4 +59,3 @@ export declare function createLoomaImageDeliveryController(options: LoomaImageDe
58
59
  reset: (editor: Editor) => void;
59
60
  };
60
61
  export declare function imageUploadContent(image: LoomaImageDescriptor): JSONContent;
61
- export {};
@@ -0,0 +1,7 @@
1
+ import type { NodeViewRenderer } from "@tiptap/core";
2
+ /** Projects the shared image's resize intent into a single document transaction.
3
+ * @ownership Delivery decorations belong to the inner image, never the figure;
4
+ * persisted attributes remain independent of host renditions and drag previews.
5
+ * @lifecycle Each atom owns a Vue app and an editability subscription.
6
+ */
7
+ export declare const imageNodeView: NodeViewRenderer;