@threadlabs/looma 0.23.24 → 0.24.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/components/ui-button/ui-button.html +1 -0
- package/dist/index.js +1 -1
- package/package.json +1 -1
- package/styles/ui-button.css +1 -0
- package/vue/UiButton.vue +1 -0
- package/vue/chunks/UiButton.js +1 -1
- package/vue/components.css +50 -49
- package/vue/editor/LoomaEditor.d.ts +4 -3
- package/vue/editor/index.js +86 -3
package/package.json
CHANGED
package/styles/ui-button.css
CHANGED
package/vue/UiButton.vue
CHANGED
|
@@ -1020,6 +1020,7 @@ function concat<T extends unknown[]>(...values: T): ConcatResult<T> {
|
|
|
1020
1020
|
[data-component~="ui-button"][data-ui-button-state~="variant=card"] {
|
|
1021
1021
|
display: grid;
|
|
1022
1022
|
grid-template-columns: auto minmax(0, 1fr) auto;
|
|
1023
|
+
align-content: center;
|
|
1023
1024
|
align-items: start;
|
|
1024
1025
|
gap: var(--ui-space-3);
|
|
1025
1026
|
inline-size: 100%;
|
package/vue/chunks/UiButton.js
CHANGED
package/vue/components.css
CHANGED
|
@@ -824,7 +824,7 @@ svg[data-v-1012d0f2],
|
|
|
824
824
|
syntax: "*";
|
|
825
825
|
inherits: false;
|
|
826
826
|
}
|
|
827
|
-
[data-component~="ui-button"][data-v-
|
|
827
|
+
[data-component~="ui-button"][data-v-ecae2439] {
|
|
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-
|
|
846
|
+
[data-component~="ui-button"][data-ui-button-state~="align=start"][data-v-ecae2439] {
|
|
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-
|
|
850
|
+
[data-component~="ui-button"][data-ui-button-state~="stretch"][data-v-ecae2439] {
|
|
851
851
|
display: flex;
|
|
852
852
|
inline-size: 100%;
|
|
853
853
|
}
|
|
854
|
-
[data-component~="ui-button"][data-v-
|
|
854
|
+
[data-component~="ui-button"][data-v-ecae2439] {
|
|
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,35 +879,35 @@ 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-
|
|
882
|
+
[data-component~="ui-button"][data-v-ecae2439]:is(a) {
|
|
883
883
|
text-decoration: none;
|
|
884
884
|
}
|
|
885
|
-
.spinner-wrap[data-v-
|
|
885
|
+
.spinner-wrap[data-v-ecae2439] {
|
|
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-
|
|
892
|
+
[data-component~="ui-button"][data-v-ecae2439] {
|
|
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-
|
|
897
|
+
[data-component~="ui-button"][data-ui-button-state~="size=xs"][data-v-ecae2439] {
|
|
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-
|
|
902
|
+
[data-component~="ui-button"][data-ui-button-state~="shape=pill"][data-v-ecae2439] {
|
|
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-
|
|
905
|
+
[data-component~="ui-button"][data-ui-button-state~="size=sm"][data-v-ecae2439] {
|
|
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-
|
|
910
|
+
[data-component~="ui-button"][data-ui-button-state~="size=lg"][data-v-ecae2439] {
|
|
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);
|
|
@@ -915,9 +915,10 @@ svg[data-v-1012d0f2],
|
|
|
915
915
|
|
|
916
916
|
/* Card actions inherit Button's palette and behavior; only their shared layout changes.
|
|
917
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-
|
|
918
|
+
[data-component~="ui-button"][data-ui-button-state~="variant=card"][data-v-ecae2439] {
|
|
919
919
|
display: grid;
|
|
920
920
|
grid-template-columns: auto minmax(0, 1fr) auto;
|
|
921
|
+
align-content: center;
|
|
921
922
|
align-items: start;
|
|
922
923
|
gap: var(--ui-space-3);
|
|
923
924
|
inline-size: 100%;
|
|
@@ -927,23 +928,23 @@ svg[data-v-1012d0f2],
|
|
|
927
928
|
box-shadow: none;
|
|
928
929
|
background-image: none;
|
|
929
930
|
}
|
|
930
|
-
[data-component~="ui-button"][data-ui-button-state~="variant=card"][data-ui-button-state~="size=lg"][data-v-
|
|
931
|
+
[data-component~="ui-button"][data-ui-button-state~="variant=card"][data-ui-button-state~="size=lg"][data-v-ecae2439] {
|
|
931
932
|
min-block-size: max(var(--ui-control-min-block-size), var(--ui-control-size-lg));
|
|
932
933
|
}
|
|
933
|
-
[data-component~="ui-button"][data-ui-button-state~="variant=card"][data-v-
|
|
934
|
+
[data-component~="ui-button"][data-ui-button-state~="variant=card"][data-v-ecae2439]:has(.card-icon:empty) {
|
|
934
935
|
grid-template-columns: minmax(0, 1fr) auto;
|
|
935
936
|
}
|
|
936
|
-
.card-icon[data-v-
|
|
937
|
+
.card-icon[data-v-ecae2439] {
|
|
937
938
|
display: flex;
|
|
938
939
|
align-items: flex-start;
|
|
939
940
|
}
|
|
940
|
-
.card-icon[data-v-
|
|
941
|
+
.card-icon[data-v-ecae2439]:empty {
|
|
941
942
|
display: none;
|
|
942
943
|
}
|
|
943
|
-
.card-content[data-v-
|
|
944
|
+
.card-content[data-v-ecae2439] {
|
|
944
945
|
min-inline-size: 0;
|
|
945
946
|
}
|
|
946
|
-
.card-action[data-v-
|
|
947
|
+
.card-action[data-v-ecae2439] {
|
|
947
948
|
display: flex;
|
|
948
949
|
align-items: center;
|
|
949
950
|
align-self: center;
|
|
@@ -951,29 +952,29 @@ svg[data-v-1012d0f2],
|
|
|
951
952
|
|
|
952
953
|
/* A link is never :enabled, so interactive states test "not unavailable" instead: for a button that
|
|
953
954
|
is exactly :enabled, at the same specificity, and it also matches a link that can be followed. */
|
|
954
|
-
[data-component~="ui-button"][data-v-
|
|
955
|
+
[data-component~="ui-button"][data-v-ecae2439]:hover:not(:disabled, [aria-disabled="true"]) {
|
|
955
956
|
border-color: var(--ui-button-hover-border, var(--_tone-hover));
|
|
956
957
|
background-color: var(--ui-button-hover-surface, oklch(from var(--_tone) l c h / 12%));
|
|
957
958
|
}
|
|
958
959
|
|
|
959
960
|
/* Pressed reads as pressed in: the highlight goes, and the shadow moves inside. */
|
|
960
|
-
[data-component~="ui-button"][data-v-
|
|
961
|
+
[data-component~="ui-button"][data-v-ecae2439]:active:not(:disabled, [aria-disabled="true"]) {
|
|
961
962
|
background-color: var(--ui-button-active-surface, oklch(from var(--_tone) l c h / 18%));
|
|
962
963
|
background-image: none;
|
|
963
964
|
box-shadow: var(--ui-pressed);
|
|
964
965
|
}
|
|
965
|
-
[data-component~="ui-button"][data-ui-button-state~="variant=solid"][data-v-
|
|
966
|
-
[data-component~="ui-button"][data-ui-button-state~="variant=danger"][data-v-
|
|
966
|
+
[data-component~="ui-button"][data-ui-button-state~="variant=solid"][data-v-ecae2439],
|
|
967
|
+
[data-component~="ui-button"][data-ui-button-state~="variant=danger"][data-v-ecae2439] {
|
|
967
968
|
border-color: var(--_tone);
|
|
968
969
|
background-color: var(--_tone);
|
|
969
970
|
color: var(--_on-tone);
|
|
970
971
|
box-shadow: var(--ui-button-solid-shadow, var(--ui-raised));
|
|
971
972
|
}
|
|
972
|
-
:is([data-component~="ui-button"][data-ui-button-state~="variant=solid"][data-v-
|
|
973
|
+
:is([data-component~="ui-button"][data-ui-button-state~="variant=solid"][data-v-ecae2439], [data-component~="ui-button"][data-ui-button-state~="variant=danger"][data-v-ecae2439]):hover:not(:disabled, [aria-disabled="true"]) {
|
|
973
974
|
background-color: var(--_tone-hover);
|
|
974
975
|
border-color: var(--_tone-hover);
|
|
975
976
|
}
|
|
976
|
-
:is([data-component~="ui-button"][data-ui-button-state~="variant=solid"][data-v-
|
|
977
|
+
:is([data-component~="ui-button"][data-ui-button-state~="variant=solid"][data-v-ecae2439], [data-component~="ui-button"][data-ui-button-state~="variant=danger"][data-v-ecae2439]):active:not(:disabled, [aria-disabled="true"]) {
|
|
977
978
|
background-color: var(--_tone-active);
|
|
978
979
|
border-color: var(--_tone-active);
|
|
979
980
|
background-image: none;
|
|
@@ -982,43 +983,43 @@ svg[data-v-1012d0f2],
|
|
|
982
983
|
|
|
983
984
|
/* Tone is the colour, variant is the volume. Every pairing works: a danger outline is a
|
|
984
985
|
destructive secondary action, not a special case. */
|
|
985
|
-
[data-component~="ui-button"][data-ui-button-state~="tone=neutral"][data-v-
|
|
986
|
+
[data-component~="ui-button"][data-ui-button-state~="tone=neutral"][data-v-ecae2439] {
|
|
986
987
|
--_hue: var(--ui-text);
|
|
987
988
|
--_hue-hover: var(--ui-text);
|
|
988
989
|
--_hue-active: var(--ui-text);
|
|
989
990
|
--_hue-text: var(--ui-text);
|
|
990
991
|
--_on-hue: var(--ui-surface);
|
|
991
992
|
}
|
|
992
|
-
[data-component~="ui-button"][data-ui-button-state~="tone=danger"][data-v-
|
|
993
|
-
[data-component~="ui-button"][data-ui-button-state~="variant=danger"][data-v-
|
|
993
|
+
[data-component~="ui-button"][data-ui-button-state~="tone=danger"][data-v-ecae2439],
|
|
994
|
+
[data-component~="ui-button"][data-ui-button-state~="variant=danger"][data-v-ecae2439] {
|
|
994
995
|
--_hue: var(--ui-danger);
|
|
995
996
|
--_hue-hover: var(--ui-danger-hover);
|
|
996
997
|
--_hue-active: var(--ui-danger-hover);
|
|
997
998
|
--_hue-text: var(--ui-danger-hover);
|
|
998
999
|
--_on-hue: var(--ui-on-danger);
|
|
999
1000
|
}
|
|
1000
|
-
[data-component~="ui-button"][data-ui-button-state~="tone=success"][data-v-
|
|
1001
|
+
[data-component~="ui-button"][data-ui-button-state~="tone=success"][data-v-ecae2439] {
|
|
1001
1002
|
--_hue: var(--ui-success);
|
|
1002
1003
|
--_hue-hover: color-mix(in srgb, var(--ui-success) 85%, #000);
|
|
1003
1004
|
--_hue-active: color-mix(in srgb, var(--ui-success) 75%, #000);
|
|
1004
1005
|
--_hue-text: color-mix(in srgb, var(--ui-success) 85%, #000);
|
|
1005
1006
|
--_on-hue: var(--ui-surface);
|
|
1006
1007
|
}
|
|
1007
|
-
[data-component~="ui-button"][data-ui-button-state~="tone=warning"][data-v-
|
|
1008
|
+
[data-component~="ui-button"][data-ui-button-state~="tone=warning"][data-v-ecae2439] {
|
|
1008
1009
|
--_hue: var(--ui-warning);
|
|
1009
1010
|
--_hue-hover: color-mix(in srgb, var(--ui-warning) 85%, #000);
|
|
1010
1011
|
--_hue-active: color-mix(in srgb, var(--ui-warning) 75%, #000);
|
|
1011
1012
|
--_hue-text: color-mix(in srgb, var(--ui-warning) 85%, #000);
|
|
1012
1013
|
--_on-hue: var(--ui-surface);
|
|
1013
1014
|
}
|
|
1014
|
-
[data-component~="ui-button"][data-ui-button-state~="tone=info"][data-v-
|
|
1015
|
+
[data-component~="ui-button"][data-ui-button-state~="tone=info"][data-v-ecae2439] {
|
|
1015
1016
|
--_hue: var(--ui-info);
|
|
1016
1017
|
--_hue-hover: color-mix(in srgb, var(--ui-info) 85%, #000);
|
|
1017
1018
|
--_hue-active: color-mix(in srgb, var(--ui-info) 75%, #000);
|
|
1018
1019
|
--_hue-text: color-mix(in srgb, var(--ui-info) 85%, #000);
|
|
1019
1020
|
--_on-hue: var(--ui-surface);
|
|
1020
1021
|
}
|
|
1021
|
-
[data-component~="ui-button"][data-ui-button-state~="variant=ghost"][data-v-
|
|
1022
|
+
[data-component~="ui-button"][data-ui-button-state~="variant=ghost"][data-v-ecae2439] {
|
|
1022
1023
|
border-color: transparent;
|
|
1023
1024
|
background: transparent;
|
|
1024
1025
|
box-shadow: none;
|
|
@@ -1026,14 +1027,14 @@ svg[data-v-1012d0f2],
|
|
|
1026
1027
|
}
|
|
1027
1028
|
|
|
1028
1029
|
/* Tints derive from the text colour, not a surface token, so hover and press show on any background. */
|
|
1029
|
-
[data-component~="ui-button"][data-ui-button-state~="variant=ghost"][data-v-
|
|
1030
|
+
[data-component~="ui-button"][data-ui-button-state~="variant=ghost"][data-v-ecae2439]:hover:not(:disabled, [aria-disabled="true"]) {
|
|
1030
1031
|
background-color: var(--ui-button-ghost-hover-surface, oklch(from var(--_tone) l c h / 10%));
|
|
1031
1032
|
border-color: transparent;
|
|
1032
1033
|
}
|
|
1033
1034
|
|
|
1034
1035
|
/* Pressed, or holding its menu open, a ghost button sinks slightly: a light surface with a faint
|
|
1035
1036
|
edge and a soft inner shadow. Lighter than hover, so an open menu's trigger never reads as heavy. */
|
|
1036
|
-
[data-component~="ui-button"][data-ui-button-state~="variant=ghost"][data-v-
|
|
1037
|
+
[data-component~="ui-button"][data-ui-button-state~="variant=ghost"][data-v-ecae2439]:is(:active, [aria-expanded="true"]):not(:disabled, [aria-disabled="true"]) {
|
|
1037
1038
|
background-color: var(
|
|
1038
1039
|
--ui-button-ghost-active-surface,
|
|
1039
1040
|
color-mix(in oklch, var(--ui-surface), var(--_tone) 3%)
|
|
@@ -1045,13 +1046,13 @@ svg[data-v-1012d0f2],
|
|
|
1045
1046
|
}
|
|
1046
1047
|
|
|
1047
1048
|
/* A soft halo that transitions in with the other box-shadow changes, layered on the raised shadow. */
|
|
1048
|
-
[data-component~="ui-button"][data-v-
|
|
1049
|
+
[data-component~="ui-button"][data-v-ecae2439]:focus-visible {
|
|
1049
1050
|
outline: none;
|
|
1050
1051
|
box-shadow:
|
|
1051
1052
|
var(--ui-button-shadow, var(--ui-raised)),
|
|
1052
1053
|
var(--ui-focus-halo);
|
|
1053
1054
|
}
|
|
1054
|
-
[data-component~="ui-button"][data-ui-button-state~="variant=solid"][data-v-
|
|
1055
|
+
[data-component~="ui-button"][data-ui-button-state~="variant=solid"][data-v-ecae2439]:focus-visible {
|
|
1055
1056
|
box-shadow:
|
|
1056
1057
|
var(--ui-button-solid-shadow, var(--ui-raised)),
|
|
1057
1058
|
var(--ui-focus-halo);
|
|
@@ -1059,7 +1060,7 @@ svg[data-v-1012d0f2],
|
|
|
1059
1060
|
|
|
1060
1061
|
/* A loading action stays visually active; only an explicitly disabled action takes the
|
|
1061
1062
|
neutral disabled palette. Its flat surface and muted ink make the state unambiguous. */
|
|
1062
|
-
[data-component~="ui-button"][data-v-
|
|
1063
|
+
[data-component~="ui-button"][data-v-ecae2439]:is(:disabled, [aria-disabled="true"]:not([aria-busy="true"])) {
|
|
1063
1064
|
border-color: var(--ui-disabled-border);
|
|
1064
1065
|
background-color: var(--ui-disabled-surface);
|
|
1065
1066
|
color: var(--ui-disabled-text);
|
|
@@ -1069,19 +1070,19 @@ svg[data-v-1012d0f2],
|
|
|
1069
1070
|
background-image: none;
|
|
1070
1071
|
box-shadow: none;
|
|
1071
1072
|
}
|
|
1072
|
-
:is([data-component~="ui-button"][data-ui-button-state~="loading"][data-v-
|
|
1073
|
+
:is([data-component~="ui-button"][data-ui-button-state~="loading"][data-v-ecae2439], [data-component~="ui-button"][data-ui-button-state~="pending"][data-v-ecae2439]):focus-visible {
|
|
1073
1074
|
box-shadow: var(--ui-focus-halo);
|
|
1074
1075
|
}
|
|
1075
1076
|
|
|
1076
1077
|
/* Ghost keeps no border, but gains the same neutral surface as other disabled buttons. */
|
|
1077
|
-
[data-component~="ui-button"][data-ui-button-state~="variant=ghost"][data-v-
|
|
1078
|
+
[data-component~="ui-button"][data-ui-button-state~="variant=ghost"][data-v-ecae2439]:is(:disabled, [aria-disabled="true"]:not([aria-busy="true"])) {
|
|
1078
1079
|
background-color: var(--ui-disabled-surface);
|
|
1079
1080
|
border-color: transparent;
|
|
1080
1081
|
}
|
|
1081
1082
|
|
|
1082
1083
|
/* The size states outrank :host, so sm is named too, or a small button would stay small. */
|
|
1083
1084
|
@media (pointer: coarse) {
|
|
1084
|
-
[data-component~="ui-button"][data-v-
|
|
1085
|
+
[data-component~="ui-button"][data-v-ecae2439], [data-component~="ui-button"][data-ui-button-state~="size=sm"][data-v-ecae2439] {
|
|
1085
1086
|
min-block-size: var(--ui-control-min-block-size);
|
|
1086
1087
|
}
|
|
1087
1088
|
}
|
|
@@ -1092,15 +1093,15 @@ svg[data-v-1012d0f2],
|
|
|
1092
1093
|
area. xs sits inside a line of text the same way, so it keeps its size and gains the area too.
|
|
1093
1094
|
Only these need it: a hit area on every button would reach over its neighbours. */
|
|
1094
1095
|
html[data-ui-input-modality="touch"]
|
|
1095
|
-
[data-component~="ui-button"][data-ui-button-state~="variant=link"][data-v-
|
|
1096
|
+
[data-component~="ui-button"][data-ui-button-state~="variant=link"][data-v-ecae2439],
|
|
1096
1097
|
html[data-ui-input-modality="touch"]
|
|
1097
|
-
[data-component~="ui-button"][data-ui-button-state~="size=xs"][data-v-
|
|
1098
|
+
[data-component~="ui-button"][data-ui-button-state~="size=xs"][data-v-ecae2439] {
|
|
1098
1099
|
position: relative;
|
|
1099
1100
|
}
|
|
1100
1101
|
html[data-ui-input-modality="touch"]
|
|
1101
|
-
[data-component~="ui-button"][data-ui-button-state~="variant=link"][data-v-
|
|
1102
|
+
[data-component~="ui-button"][data-ui-button-state~="variant=link"][data-v-ecae2439]::after,
|
|
1102
1103
|
html[data-ui-input-modality="touch"]
|
|
1103
|
-
[data-component~="ui-button"][data-ui-button-state~="size=xs"][data-v-
|
|
1104
|
+
[data-component~="ui-button"][data-ui-button-state~="size=xs"][data-v-ecae2439]::after {
|
|
1104
1105
|
position: absolute;
|
|
1105
1106
|
inset: 50% auto auto 50%;
|
|
1106
1107
|
inline-size: max(100%, var(--ui-control-min-block-size));
|
|
@@ -1111,7 +1112,7 @@ html[data-ui-input-modality="touch"]
|
|
|
1111
1112
|
|
|
1112
1113
|
/* Link: an inline text action. No box, padding, or minimum height (inline targets are exempt under
|
|
1113
1114
|
WCAG 2.5.8); the inherited font; an underline that strengthens on hover; a visible focus ring. */
|
|
1114
|
-
[data-component~="ui-button"][data-ui-button-state~="variant=link"][data-v-
|
|
1115
|
+
[data-component~="ui-button"][data-ui-button-state~="variant=link"][data-v-ecae2439] {
|
|
1115
1116
|
display: inline;
|
|
1116
1117
|
min-block-size: 0;
|
|
1117
1118
|
padding: 0;
|
|
@@ -1129,21 +1130,21 @@ html[data-ui-input-modality="touch"]
|
|
|
1129
1130
|
color var(--ui-motion-fast) var(--ui-motion-ease),
|
|
1130
1131
|
text-decoration-color var(--ui-motion-fast) var(--ui-motion-ease);
|
|
1131
1132
|
}
|
|
1132
|
-
[data-component~="ui-button"][data-ui-button-state~="variant=link"][data-v-
|
|
1133
|
+
[data-component~="ui-button"][data-ui-button-state~="variant=link"][data-v-ecae2439]:hover:not(:disabled, [aria-disabled="true"]) {
|
|
1133
1134
|
color: var(--ui-button-link-hover-text, var(--ui-text-primary));
|
|
1134
1135
|
text-decoration-color: currentColor;
|
|
1135
1136
|
background: none;
|
|
1136
1137
|
transform: none;
|
|
1137
1138
|
}
|
|
1138
|
-
[data-component~="ui-button"][data-ui-button-state~="variant=link"][data-v-
|
|
1139
|
+
[data-component~="ui-button"][data-ui-button-state~="variant=link"][data-v-ecae2439]:active:not(:disabled, [aria-disabled="true"]) {
|
|
1139
1140
|
background: none;
|
|
1140
1141
|
transform: none;
|
|
1141
1142
|
}
|
|
1142
|
-
[data-component~="ui-button"][data-ui-button-state~="variant=link"][data-v-
|
|
1143
|
+
[data-component~="ui-button"][data-ui-button-state~="variant=link"][data-v-ecae2439]:is(:disabled, [aria-disabled="true"]:not([aria-busy="true"])) {
|
|
1143
1144
|
background: none;
|
|
1144
1145
|
border-color: transparent;
|
|
1145
1146
|
}
|
|
1146
|
-
[data-component~="ui-button"][data-ui-button-state~="variant=link"][data-v-
|
|
1147
|
+
[data-component~="ui-button"][data-ui-button-state~="variant=link"][data-v-ecae2439]:focus-visible {
|
|
1147
1148
|
box-shadow: none;
|
|
1148
1149
|
/* Fallback keeps the ring if the token is missing: an unresolved var() would void the outline. */
|
|
1149
1150
|
outline: var(--ui-focus-width) solid var(--ui-focus-ring);
|
|
@@ -1151,7 +1152,7 @@ html[data-ui-input-modality="touch"]
|
|
|
1151
1152
|
}
|
|
1152
1153
|
|
|
1153
1154
|
/* A component that sets its display still honours the hidden attribute on its root. */
|
|
1154
|
-
[data-component~="ui-button"][hidden][data-v-
|
|
1155
|
+
[data-component~="ui-button"][hidden][data-v-ecae2439] {
|
|
1155
1156
|
display: none;
|
|
1156
1157
|
}
|
|
1157
1158
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import "./looma-editor.css";
|
|
2
|
-
import type
|
|
2
|
+
import { type AnyExtension, type Editor, type JSONContent } from "@tiptap/core";
|
|
3
3
|
import { type PropType, type VNode } from "vue";
|
|
4
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";
|
|
@@ -23,8 +23,9 @@ export type LoomaImageUploader = (file: File) => Promise<string | LoomaImageUplo
|
|
|
23
23
|
/**
|
|
24
24
|
* Chooses whether formatting controls follow a selection, occupy persistent
|
|
25
25
|
* editor chrome, or open from an app-owned button (`popover`, with a text-only
|
|
26
|
-
* bubble for selections).
|
|
27
|
-
*
|
|
26
|
+
* bubble for selections). Floating tools open on text selection or a held press
|
|
27
|
+
* and dismiss on Escape/caret movement. `contextual` includes the full command
|
|
28
|
+
* set. Narrow viewports retain a dock; document commands/content are unchanged.
|
|
28
29
|
*/
|
|
29
30
|
export type LoomaEditorToolbarMode = "bubble" | "sticky" | "popover" | "contextual";
|
|
30
31
|
/**
|
package/vue/editor/index.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { computed, createApp, defineComponent, h, nextTick, onBeforeUnmount, onMounted, reactive, ref, shallowRef, useId, watch } from "vue";
|
|
2
|
-
import { DEFAULT_MENTION_RESULT_LIMIT, LOOMA_CHIP_COLORS, LOOMA_ICONS, createLoomaMentionExtension, createLoomaSlashCommandExtension, getActiveTableUiState, getDefaultEditorExtensions, getDefaultSlashCommands, getTableOfContentsEntries, getVisualViewportRect, handleTableAction, handleTableOverlayAction, measureTableOverlayGeometry, normalizeActiveTableColumnWidths, normalizeLoomaChipColor, normalizeTableOfContentsConfig, resolveTableCellAt,
|
|
2
|
+
import { DEFAULT_MENTION_RESULT_LIMIT, LOOMA_CHIP_COLORS, LOOMA_ICONS, createLoomaMentionExtension, createLoomaSlashCommandExtension, getActiveTableUiState, getDefaultEditorExtensions, getDefaultSlashCommands, getTableOfContentsEntries, getVisualViewportRect, handleTableAction, handleTableOverlayAction, measureTableOverlayGeometry, normalizeActiveTableColumnWidths, normalizeLoomaChipColor, normalizeTableOfContentsConfig, resolveTableCellAt, siteRelativeHref } from "@threadlabs/looma/editor";
|
|
3
3
|
import { Button, Card, Checkbox, Cluster, Combobox, Disclosure, EditorInsertTableGrid, EditorMentionMenu, EditorMentionMenuItem, EditorSlashMenu, EditorSlashMenuGroup, EditorSlashMenuItem, EditorTableContextMenu, EditorTableOverlay, EditorTableToolbar, EditorToolbar, EditorToolbar as EditorToolbar$1, FormField, Icon, IconButton, Image, Input, InputGroup, List, Menu, MenuItem, Popover, ScrollArea, SearchResultRow, Separator, Stack, Text, Tooltip } from "@threadlabs/looma/vue";
|
|
4
|
+
import { Extension, Node as Node$1, getText, getTextSerializersFromSchema, mergeAttributes, nodeInputRule, posToDOMRect } from "@tiptap/core";
|
|
4
5
|
import { BubbleMenu, EditorContent, useEditor } from "@tiptap/vue-3";
|
|
5
6
|
import { closeHistory } from "@tiptap/pm/history";
|
|
6
7
|
import { createLowlight } from "lowlight";
|
|
7
8
|
import { NodeSelection, Plugin, PluginKey, TextSelection } from "@tiptap/pm/state";
|
|
8
|
-
import { Extension, Node as Node$1, getText, getTextSerializersFromSchema, mergeAttributes, nodeInputRule } from "@tiptap/core";
|
|
9
9
|
import { Decoration, DecorationSet } from "@tiptap/pm/view";
|
|
10
10
|
import { DOMParser, DOMSerializer, Fragment as Fragment$1 } from "@tiptap/pm/model";
|
|
11
11
|
export * from "@threadlabs/looma/editor";
|
|
@@ -1795,6 +1795,21 @@ var LoomaEditor = defineComponent({
|
|
|
1795
1795
|
const mobileToolbarStyle = ref({});
|
|
1796
1796
|
const mobile = ref(typeof window !== "undefined" && window.innerWidth <= 767);
|
|
1797
1797
|
const editorFocused = ref(false);
|
|
1798
|
+
let dismissedFormattingSelection = null;
|
|
1799
|
+
let heldFormattingSelection = null;
|
|
1800
|
+
let formattingPopup = null;
|
|
1801
|
+
let heldPressTimer;
|
|
1802
|
+
let heldPress = null;
|
|
1803
|
+
const floatingFormattingMode = () => props.toolbarMode !== "sticky" && !(props.toolbarMode === "popover" && props.toolbarOpen) && !mobile.value;
|
|
1804
|
+
const cancelHeldPress = () => {
|
|
1805
|
+
clearTimeout(heldPressTimer);
|
|
1806
|
+
heldPressTimer = void 0;
|
|
1807
|
+
heldPress = null;
|
|
1808
|
+
};
|
|
1809
|
+
const formattingSelectionActive = (instance) => {
|
|
1810
|
+
const selection = instance.state.selection;
|
|
1811
|
+
return selection instanceof TextSelection && !dismissedFormattingSelection?.eq(selection) && (!selection.empty || Boolean(heldFormattingSelection?.eq(selection)));
|
|
1812
|
+
};
|
|
1798
1813
|
const editorStateVersion = ref(0);
|
|
1799
1814
|
const mobileToolbarMode = ref("formatting");
|
|
1800
1815
|
let dragLeaveTimer = null;
|
|
@@ -1995,6 +2010,12 @@ var LoomaEditor = defineComponent({
|
|
|
1995
2010
|
onFocus: ({ editor: instance }) => rememberSelection(instance),
|
|
1996
2011
|
onSelectionUpdate: ({ editor: instance }) => {
|
|
1997
2012
|
rememberSelection(instance);
|
|
2013
|
+
if (floatingFormattingMode()) {
|
|
2014
|
+
const selection = instance.state.selection;
|
|
2015
|
+
if (!dismissedFormattingSelection?.eq(selection)) dismissedFormattingSelection = null;
|
|
2016
|
+
if (!heldFormattingSelection?.eq(selection)) heldFormattingSelection = null;
|
|
2017
|
+
if (!formattingSelectionActive(instance)) formattingPopup?.hide();
|
|
2018
|
+
}
|
|
1998
2019
|
if (linkContextEditing.value && instance.state.selection.from !== linkSelection?.from) linkContextEditing.value = false;
|
|
1999
2020
|
},
|
|
2000
2021
|
onTransaction: () => {
|
|
@@ -2442,7 +2463,10 @@ var LoomaEditor = defineComponent({
|
|
|
2442
2463
|
editor.value?.commands.dismissLoomaSlashMenu();
|
|
2443
2464
|
});
|
|
2444
2465
|
watch(() => props.editable, (editable) => {
|
|
2466
|
+
cancelHeldPress();
|
|
2467
|
+
heldFormattingSelection = null;
|
|
2445
2468
|
editor.value?.setEditable(editable);
|
|
2469
|
+
if (!editable) formattingPopup?.hide();
|
|
2446
2470
|
if (editor.value) imageDelivery.reset(editor.value);
|
|
2447
2471
|
root.value?.querySelectorAll("[data-looma-chip]").forEach((chip) => {
|
|
2448
2472
|
chip.setAttribute("aria-disabled", String(!editable));
|
|
@@ -2638,10 +2662,12 @@ var LoomaEditor = defineComponent({
|
|
|
2638
2662
|
updateMobileViewport();
|
|
2639
2663
|
};
|
|
2640
2664
|
const onEditorBlur = () => {
|
|
2665
|
+
cancelHeldPress();
|
|
2641
2666
|
queueMicrotask(updateTableUi);
|
|
2642
2667
|
updateCodeUi();
|
|
2643
2668
|
setTimeout(() => {
|
|
2644
2669
|
if (!tableInteractionActive && !editor.value?.isFocused) editorFocused.value = false;
|
|
2670
|
+
if (!editor.value?.isFocused && !formattingPopup?.popper.contains(document.activeElement)) heldFormattingSelection = null;
|
|
2645
2671
|
}, 0);
|
|
2646
2672
|
};
|
|
2647
2673
|
const bindEditorUi = (instance) => {
|
|
@@ -2667,9 +2693,13 @@ var LoomaEditor = defineComponent({
|
|
|
2667
2693
|
}, { immediate: true });
|
|
2668
2694
|
const onViewportChange = (event) => {
|
|
2669
2695
|
if (event?.type === "scroll" && event.target instanceof Node && (tableToolbarShell.value?.contains(event.target) || tableMenuShell.value?.contains(event.target))) return;
|
|
2696
|
+
cancelHeldPress();
|
|
2697
|
+
const wasMobile = mobile.value;
|
|
2670
2698
|
updateMobileViewport();
|
|
2699
|
+
if (wasMobile !== mobile.value) heldFormattingSelection = null;
|
|
2671
2700
|
updateTableUi();
|
|
2672
2701
|
updateCodeUi();
|
|
2702
|
+
if (floatingFormattingMode() && editor.value && !formattingSelectionActive(editor.value)) formattingPopup?.hide();
|
|
2673
2703
|
tableUi.menuOpen = false;
|
|
2674
2704
|
};
|
|
2675
2705
|
const onDocumentPointerDown = (event) => {
|
|
@@ -2682,6 +2712,40 @@ var LoomaEditor = defineComponent({
|
|
|
2682
2712
|
}, 0);
|
|
2683
2713
|
if ((tableUi.toolbarOpen || tableUi.overlayOpen || tableUi.menuOpen) && !inElement(root.value) && !inTableUi) closeTableUi();
|
|
2684
2714
|
};
|
|
2715
|
+
const onFormattingPointerDown = (event) => {
|
|
2716
|
+
cancelHeldPress();
|
|
2717
|
+
const instance = editor.value;
|
|
2718
|
+
if (!floatingFormattingMode() || tableResizeActive || !props.editable || !instance || event.button !== 0 || !event.isPrimary || !(event.target instanceof Element) || !instance.view.dom.contains(event.target) || event.target.closest("button, input, select, textarea, [data-looma-image-node]")) return;
|
|
2719
|
+
heldPress = {
|
|
2720
|
+
id: event.pointerId,
|
|
2721
|
+
x: event.clientX,
|
|
2722
|
+
y: event.clientY
|
|
2723
|
+
};
|
|
2724
|
+
heldPressTimer = setTimeout(() => {
|
|
2725
|
+
heldPressTimer = void 0;
|
|
2726
|
+
const selection = instance.state.selection;
|
|
2727
|
+
if (!heldPress || !floatingFormattingMode() || tableResizeActive || !props.editable || !instance.isFocused || slash.active || mention.active || !(selection instanceof TextSelection)) return;
|
|
2728
|
+
dismissedFormattingSelection = null;
|
|
2729
|
+
heldFormattingSelection = selection;
|
|
2730
|
+
formattingPopup?.setProps({ getReferenceClientRect: () => posToDOMRect(instance.view, selection.from, selection.to) });
|
|
2731
|
+
formattingPopup?.show();
|
|
2732
|
+
}, 500);
|
|
2733
|
+
};
|
|
2734
|
+
const onFormattingPointerMove = (event) => {
|
|
2735
|
+
if (heldPress?.id === event.pointerId && Math.hypot(event.clientX - heldPress.x, event.clientY - heldPress.y) > 8) cancelHeldPress();
|
|
2736
|
+
};
|
|
2737
|
+
const onFormattingPointerEnd = (event) => {
|
|
2738
|
+
if (heldPress?.id === event.pointerId) cancelHeldPress();
|
|
2739
|
+
};
|
|
2740
|
+
const onFormattingKeyDown = (event) => {
|
|
2741
|
+
cancelHeldPress();
|
|
2742
|
+
if (event.key !== "Escape" || !floatingFormattingMode() || !(event.target instanceof Node) || !(root.value?.contains(event.target) || formattingPopup?.popper.contains(event.target))) return;
|
|
2743
|
+
const selection = editor.value?.state.selection;
|
|
2744
|
+
dismissedFormattingSelection = selection instanceof TextSelection ? selection : null;
|
|
2745
|
+
heldFormattingSelection = null;
|
|
2746
|
+
formattingPopup?.hide();
|
|
2747
|
+
hideTool();
|
|
2748
|
+
};
|
|
2685
2749
|
const onResizePointerDown = (event) => {
|
|
2686
2750
|
tableResizeActive = event.target instanceof HTMLElement && event.target.classList.contains("column-resize-handle");
|
|
2687
2751
|
};
|
|
@@ -2770,6 +2834,11 @@ var LoomaEditor = defineComponent({
|
|
|
2770
2834
|
window.visualViewport?.addEventListener("resize", onViewportChange);
|
|
2771
2835
|
window.visualViewport?.addEventListener("scroll", onViewportChange);
|
|
2772
2836
|
document.addEventListener("pointerdown", onDocumentPointerDown, true);
|
|
2837
|
+
document.addEventListener("pointerdown", onFormattingPointerDown);
|
|
2838
|
+
document.addEventListener("pointermove", onFormattingPointerMove);
|
|
2839
|
+
document.addEventListener("pointerup", onFormattingPointerEnd);
|
|
2840
|
+
document.addEventListener("pointercancel", onFormattingPointerEnd);
|
|
2841
|
+
document.addEventListener("keydown", onFormattingKeyDown);
|
|
2773
2842
|
document.addEventListener("pointerdown", onResizePointerDown, true);
|
|
2774
2843
|
document.addEventListener("pointerup", onResizePointerUp, true);
|
|
2775
2844
|
root.value?.addEventListener("error", onImageError, true);
|
|
@@ -2779,7 +2848,14 @@ var LoomaEditor = defineComponent({
|
|
|
2779
2848
|
window.removeEventListener("scroll", onViewportChange, true);
|
|
2780
2849
|
window.visualViewport?.removeEventListener("resize", onViewportChange);
|
|
2781
2850
|
window.visualViewport?.removeEventListener("scroll", onViewportChange);
|
|
2851
|
+
cancelHeldPress();
|
|
2852
|
+
formattingPopup = null;
|
|
2782
2853
|
document.removeEventListener("pointerdown", onDocumentPointerDown, true);
|
|
2854
|
+
document.removeEventListener("pointerdown", onFormattingPointerDown);
|
|
2855
|
+
document.removeEventListener("pointermove", onFormattingPointerMove);
|
|
2856
|
+
document.removeEventListener("pointerup", onFormattingPointerEnd);
|
|
2857
|
+
document.removeEventListener("pointercancel", onFormattingPointerEnd);
|
|
2858
|
+
document.removeEventListener("keydown", onFormattingKeyDown);
|
|
2783
2859
|
document.removeEventListener("pointerdown", onResizePointerDown, true);
|
|
2784
2860
|
document.removeEventListener("pointerup", onResizePointerUp, true);
|
|
2785
2861
|
root.value?.removeEventListener("error", onImageError, true);
|
|
@@ -3131,8 +3207,15 @@ var LoomaEditor = defineComponent({
|
|
|
3131
3207
|
instance && props.editable && !mobile.value && (props.toolbarMode === "bubble" || props.toolbarMode === "popover" || props.toolbarMode === "contextual") ? h(BubbleMenu, {
|
|
3132
3208
|
editor: instance,
|
|
3133
3209
|
pluginKey: "looma-text-formatting-menu",
|
|
3134
|
-
|
|
3210
|
+
updateDelay: 0,
|
|
3211
|
+
shouldShow: ({ editor: menuEditor }) => (props.toolbarMode !== "popover" || !props.toolbarOpen) && !slash.active && !mention.active && (menuEditor.isFocused || linkContextEditing.value) && formattingSelectionActive(menuEditor),
|
|
3135
3212
|
tippyOptions: {
|
|
3213
|
+
onCreate: (popup) => {
|
|
3214
|
+
formattingPopup = popup;
|
|
3215
|
+
},
|
|
3216
|
+
onDestroy: () => {
|
|
3217
|
+
formattingPopup = null;
|
|
3218
|
+
},
|
|
3136
3219
|
appendTo: () => root.value?.closest("dialog[open], [popover]") ?? document.body,
|
|
3137
3220
|
onShow: () => announceOverlayOpen(root.value?.ownerDocument ?? document, root.value),
|
|
3138
3221
|
duration: 100,
|