@quillmark/svelte 0.3.1 → 0.3.2
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/dist/core/codec/anchor.d.ts +5 -5
- package/dist/core/codec/anchor.js +37 -13
- package/dist/core/theme.css +9 -5
- package/dist/visual/Card.svelte +7 -7
- package/dist/visual/FormatPopover.svelte +58 -37
- package/dist/visual/ProseField.svelte +5 -0
- package/dist/visual/TipsCard.svelte +10 -4
- package/package.json +1 -1
|
@@ -8,11 +8,11 @@ export interface RangeAnchor {
|
|
|
8
8
|
/**
|
|
9
9
|
* A virtual anchor over one PM range, measured when asked rather than when made.
|
|
10
10
|
*
|
|
11
|
-
* The measure is total.
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
11
|
+
* The measure is total. It reads layout, and layout can be gone: a position the
|
|
12
|
+
* document has since invalidated, a leaf inside a collapsed section, a view torn down
|
|
13
|
+
* between a scroll firing and the measure running. `autoUpdate` calls this at moments
|
|
14
|
+
* none of its callers choose, so an unmeasurable anchor returns the last rect that
|
|
15
|
+
* measured rather than throwing out of floating-ui's positioning pass.
|
|
16
16
|
*
|
|
17
17
|
* `from === to` is a caret, which is what the slash menu hangs off.
|
|
18
18
|
*/
|
|
@@ -1,11 +1,42 @@
|
|
|
1
|
+
function union(a, b) {
|
|
2
|
+
return {
|
|
3
|
+
left: Math.min(a.left, b.left),
|
|
4
|
+
top: Math.min(a.top, b.top),
|
|
5
|
+
right: Math.max(a.right, b.right),
|
|
6
|
+
bottom: Math.max(a.bottom, b.bottom)
|
|
7
|
+
};
|
|
8
|
+
}
|
|
9
|
+
function caretBox(view, pos) {
|
|
10
|
+
const { left, top, right, bottom } = view.coordsAtPos(pos);
|
|
11
|
+
return { left, top, right, bottom };
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* The box the highlight paints, and not the two ends' caret boxes: a selection that
|
|
15
|
+
* wraps has its head and its anchor on opposite sides of the block, so their union
|
|
16
|
+
* names a rectangle no text is under.
|
|
17
|
+
*
|
|
18
|
+
* A range measuring to nothing falls back to that union. A leaf the view draws no text
|
|
19
|
+
* for has no painted box, and an approximate anchor beats one of zero.
|
|
20
|
+
*/
|
|
21
|
+
function selectionBox(view, from, to) {
|
|
22
|
+
const range = document.createRange();
|
|
23
|
+
const start = view.domAtPos(from);
|
|
24
|
+
const end = view.domAtPos(to);
|
|
25
|
+
range.setStart(start.node, start.offset);
|
|
26
|
+
range.setEnd(end.node, end.offset);
|
|
27
|
+
const { left, top, right, bottom, width, height } = range.getBoundingClientRect();
|
|
28
|
+
if (width || height)
|
|
29
|
+
return { left, top, right, bottom };
|
|
30
|
+
return union(caretBox(view, from), caretBox(view, to));
|
|
31
|
+
}
|
|
1
32
|
/**
|
|
2
33
|
* A virtual anchor over one PM range, measured when asked rather than when made.
|
|
3
34
|
*
|
|
4
|
-
* The measure is total.
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
35
|
+
* The measure is total. It reads layout, and layout can be gone: a position the
|
|
36
|
+
* document has since invalidated, a leaf inside a collapsed section, a view torn down
|
|
37
|
+
* between a scroll firing and the measure running. `autoUpdate` calls this at moments
|
|
38
|
+
* none of its callers choose, so an unmeasurable anchor returns the last rect that
|
|
39
|
+
* measured rather than throwing out of floating-ui's positioning pass.
|
|
9
40
|
*
|
|
10
41
|
* `from === to` is a caret, which is what the slash menu hangs off.
|
|
11
42
|
*/
|
|
@@ -18,14 +49,7 @@ export function rangeAnchor(view, from, to) {
|
|
|
18
49
|
contextElement: view.dom,
|
|
19
50
|
getBoundingClientRect: () => {
|
|
20
51
|
try {
|
|
21
|
-
|
|
22
|
-
const b = view.coordsAtPos(to);
|
|
23
|
-
last = {
|
|
24
|
-
left: Math.min(a.left, b.left),
|
|
25
|
-
top: Math.min(a.top, b.top),
|
|
26
|
-
right: Math.max(a.right, b.right),
|
|
27
|
-
bottom: Math.max(a.bottom, b.bottom)
|
|
28
|
-
};
|
|
52
|
+
last = from === to ? caretBox(view, from) : selectionBox(view, from, to);
|
|
29
53
|
}
|
|
30
54
|
catch {
|
|
31
55
|
// unmeasurable: keep the last good rect
|
package/dist/core/theme.css
CHANGED
|
@@ -184,16 +184,20 @@
|
|
|
184
184
|
draft (VISUAL_EDITOR §"Card operations"). A literal rather than a dial
|
|
185
185
|
because there is nothing left to turn: the rungs under it mix over
|
|
186
186
|
the surface and the ink a consumer already tuned, so the card follows a
|
|
187
|
-
brand untold. The mixes are faint on purpose: a warmth, not a badge.
|
|
187
|
+
brand untold. The mixes are faint on purpose: a warmth, not a badge.
|
|
188
|
+
|
|
189
|
+
The one group mixed in sRGB. oklab holds perceived lightness across a mix and
|
|
190
|
+
spends chroma to do it, which is right where both endpoints are grey and wrong
|
|
191
|
+
for a hue: at either pole it lands the ink a channel short of warm. */
|
|
188
192
|
--_qm-tips: #e88c0a;
|
|
189
|
-
--_qm-tips-surface: color-mix(in
|
|
190
|
-
--_qm-tips-ink: color-mix(in
|
|
193
|
+
--_qm-tips-surface: color-mix(in srgb, var(--_qm-surface), var(--_qm-tips) 3%);
|
|
194
|
+
--_qm-tips-ink: color-mix(in srgb, var(--_qm-ink), var(--_qm-tips) 24%);
|
|
191
195
|
/* The ink and the amber stepped up, and the pair is spent on the two runs that
|
|
192
196
|
are not the sentence: a token in a tip, and the foot's control under the
|
|
193
197
|
pointer. One rung each rather than one per run, a chip and a hover four
|
|
194
198
|
percent apart being a number with no second decision under it. */
|
|
195
|
-
--_qm-tips-ink-warm: color-mix(in
|
|
196
|
-
--_qm-tips-fill: color-mix(in
|
|
199
|
+
--_qm-tips-ink-warm: color-mix(in srgb, var(--_qm-ink), var(--_qm-tips) 45%);
|
|
200
|
+
--_qm-tips-fill: color-mix(in srgb, var(--_qm-tips) 12%, transparent);
|
|
197
201
|
/* The typewriter, the half of the slip the amber cannot carry: a modern mono
|
|
198
202
|
reads as code where a Courier reads as typed. Its own rung rather than
|
|
199
203
|
`--_qm-font-mono`, the chrome's readout face, which a consumer turns for
|
package/dist/visual/Card.svelte
CHANGED
|
@@ -390,11 +390,12 @@
|
|
|
390
390
|
{/snippet}
|
|
391
391
|
|
|
392
392
|
<style>
|
|
393
|
-
/* An island
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
393
|
+
/* An island: the card is the base plane, the column behind it the sunken one, and the
|
|
394
|
+
hairline closes it (ARCHITECTURE §Styling). The inset is the same rung as the gap, so
|
|
395
|
+
what separates the header from the fields is what separates the fields from the
|
|
396
|
+
card's edge. */
|
|
397
397
|
.qm-card {
|
|
398
|
+
border: var(--_qm-border-width) solid var(--_qm-border);
|
|
398
399
|
border-radius: var(--_qm-radius);
|
|
399
400
|
padding: var(--_qm-space-3);
|
|
400
401
|
background: var(--_qm-surface);
|
|
@@ -639,10 +640,9 @@
|
|
|
639
640
|
flex-direction: column;
|
|
640
641
|
gap: var(--_qm-space);
|
|
641
642
|
}
|
|
642
|
-
/* The
|
|
643
|
-
around this one is a state rather than heavier chrome. */
|
|
643
|
+
/* The state is in the stroke's pattern, every card being closed by one already. */
|
|
644
644
|
.qm-card.qm-unschemable {
|
|
645
|
-
border:
|
|
645
|
+
border-style: dashed;
|
|
646
646
|
}
|
|
647
647
|
/* One typographic role with the editable title above. */
|
|
648
648
|
.qm-card-title-static {
|
|
@@ -19,10 +19,15 @@
|
|
|
19
19
|
transient `selectionchange` events first). `focusout` keeps the popover honest
|
|
20
20
|
when focus leaves the leaf.
|
|
21
21
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
22
|
+
A press holds it down, which is what makes the surface answer a selection rather
|
|
23
|
+
than a pointer: a drag fires `selectionchange` on every frame, so a surface driven
|
|
24
|
+
by that alone rises on the first character and rides the cursor across the text it
|
|
25
|
+
is covering. Keyboard selection holds nothing, so a Shift-arrow syncs as it fires.
|
|
26
|
+
|
|
27
|
+
Nothing here watches scroll. `sync` decides whether the surface is up, which a
|
|
28
|
+
scroll does not change; where it sits is the anchor's own question, and the anchor
|
|
29
|
+
answers it by measuring when floating-ui asks rather than by being handed a rect
|
|
30
|
+
(`codec/anchor.ts`).
|
|
26
31
|
|
|
27
32
|
Focus discipline. Three failure modes a naive Popover.Content invites:
|
|
28
33
|
(1) its default `trapFocus` (true) redirects any focus landing outside its
|
|
@@ -76,7 +81,6 @@
|
|
|
76
81
|
}
|
|
77
82
|
let { getActiveLeaf }: Props = $props();
|
|
78
83
|
|
|
79
|
-
/** Mark size: the shared control-glyph rule for the popover icons. */
|
|
80
84
|
const GLYPH = 15;
|
|
81
85
|
/** The six content formatting marks. `anchor` is a seventh, drawn separately below:
|
|
82
86
|
* a decoration toggle rather than a PM `toggleMark`. One string per mark carries both
|
|
@@ -91,11 +95,14 @@
|
|
|
91
95
|
]);
|
|
92
96
|
|
|
93
97
|
let open = $state(false);
|
|
94
|
-
|
|
98
|
+
/** A pointer is down outside this surface: the selection is still being made. */
|
|
99
|
+
let pressed = false;
|
|
95
100
|
/** Whether the selection is in the field's coordinate space, which an anchor needs
|
|
96
101
|
* and a table cell is not (see `sync`). */
|
|
97
102
|
let anchorAvailable = $state(true);
|
|
98
103
|
let linkPromptOpen = $state(false);
|
|
104
|
+
/** Whether the selection carried a link when the prompt was raised. */
|
|
105
|
+
let linkPresent = $state(false);
|
|
99
106
|
let linkValue = $state('');
|
|
100
107
|
let linkInputEl = $state<HTMLInputElement | undefined>(undefined);
|
|
101
108
|
let contentEl = $state<HTMLElement | undefined>(undefined);
|
|
@@ -118,7 +125,6 @@
|
|
|
118
125
|
return leaf?.focusedView?.() ?? leaf?.view;
|
|
119
126
|
}
|
|
120
127
|
|
|
121
|
-
/** Recompute open/anchor/active-marks from the active leaf's current PM selection. */
|
|
122
128
|
function sync(): void {
|
|
123
129
|
const insidePopover =
|
|
124
130
|
!!contentEl && !!document.activeElement && contentEl.contains(document.activeElement);
|
|
@@ -126,6 +132,7 @@
|
|
|
126
132
|
// Costs nothing to hold: the anchor minted below goes on measuring while this
|
|
127
133
|
// returns early, so a surface frozen here still tracks the selection it covers.
|
|
128
134
|
if (insidePopover) return;
|
|
135
|
+
if (pressed) return;
|
|
129
136
|
const leaf = getActiveLeaf() as LeafWithView | undefined;
|
|
130
137
|
const view = leaf?.focusedView?.() ?? leaf?.view;
|
|
131
138
|
// A non-empty text selection. A node selection is non-empty too and has nothing
|
|
@@ -133,7 +140,7 @@
|
|
|
133
140
|
// Escape out of a table cell lands.
|
|
134
141
|
const selection = view?.state.selection;
|
|
135
142
|
const formattable = selection instanceof TextSelection && !selection.empty;
|
|
136
|
-
if (!view || !view.hasFocus() || !formattable) {
|
|
143
|
+
if (!view || !view.hasFocus() || !formattable || !hasText(view)) {
|
|
137
144
|
open = false;
|
|
138
145
|
linkPromptOpen = false;
|
|
139
146
|
return;
|
|
@@ -141,27 +148,21 @@
|
|
|
141
148
|
portalTarget = view.dom.closest<HTMLElement>('[data-qm-root]') ?? undefined;
|
|
142
149
|
const { from, to } = view.state.selection;
|
|
143
150
|
anchor = rangeAnchor(view, from, to);
|
|
144
|
-
|
|
145
|
-
for
|
|
146
|
-
const type = view.state.schema.marks[m.name];
|
|
147
|
-
marks[m.name] = !!type && view.state.doc.rangeHasMark(from, to, type);
|
|
148
|
-
}
|
|
149
|
-
// `anchor` is a decoration, not a PM mark (CODEC §Marks): its active state is
|
|
150
|
-
// whether the selection covers an identity anchor, read off the leaf in USV.
|
|
151
|
-
//
|
|
152
|
-
// A selection inside a table cell is not in that coordinate space at all: the
|
|
153
|
-
// field's position map holds one `atom` run for the whole island, so there is
|
|
151
|
+
// A selection inside a table cell is not in the field's coordinate space at all:
|
|
152
|
+
// the field's position map holds one `atom` run for the whole island, so there is
|
|
154
153
|
// no USV offset to mint an anchor at. The button is withheld rather than
|
|
155
154
|
// disabled, because what it would toggle does not exist there (CODEC §Islands).
|
|
156
155
|
anchorAvailable = !!leaf && view === leaf.view;
|
|
157
|
-
if (leaf && anchorAvailable) {
|
|
158
|
-
const sel = leaf.selectionRange();
|
|
159
|
-
marks.anchor = leaf.anchorsInRange(sel.from, sel.to).length > 0;
|
|
160
|
-
}
|
|
161
|
-
activeMarks = marks;
|
|
162
156
|
open = true;
|
|
163
157
|
}
|
|
164
158
|
|
|
159
|
+
/** Whitespace alone is a selection the pointer makes by accident, at the end of a
|
|
160
|
+
* line or between two words, and every mark over it is invisible. */
|
|
161
|
+
function hasText(view: EditorView): boolean {
|
|
162
|
+
const { from, to } = view.state.selection;
|
|
163
|
+
return view.state.doc.textBetween(from, to, ' ', ' ').trim().length > 0;
|
|
164
|
+
}
|
|
165
|
+
|
|
165
166
|
// A burst of DOM events (verified: browser "select all" processing fires
|
|
166
167
|
// `selectionchange` several times, transiently empty before landing on the
|
|
167
168
|
// final range) must coalesce to one settled check, not one `sync()` per
|
|
@@ -186,12 +187,33 @@
|
|
|
186
187
|
});
|
|
187
188
|
}
|
|
188
189
|
|
|
190
|
+
function onPointerDown(e: PointerEvent): void {
|
|
191
|
+
// A press on the surface is not a selection gesture, and lowering it here would
|
|
192
|
+
// take the button out from under the pointer before its click.
|
|
193
|
+
if (contentEl?.contains(e.target as Node)) return;
|
|
194
|
+
pressed = true;
|
|
195
|
+
open = false;
|
|
196
|
+
}
|
|
197
|
+
function onPointerUp(): void {
|
|
198
|
+
if (!pressed) return;
|
|
199
|
+
pressed = false;
|
|
200
|
+
deferredSync();
|
|
201
|
+
}
|
|
202
|
+
|
|
189
203
|
$effect(() => {
|
|
190
204
|
document.addEventListener('selectionchange', deferredSync);
|
|
191
205
|
document.addEventListener('focusout', deferredSync);
|
|
206
|
+
// Capture, so the gesture is seen whatever a view in the middle of it does with
|
|
207
|
+
// the event. `pointercancel` is the release a gesture the browser takes over gets.
|
|
208
|
+
document.addEventListener('pointerdown', onPointerDown, true);
|
|
209
|
+
document.addEventListener('pointerup', onPointerUp, true);
|
|
210
|
+
document.addEventListener('pointercancel', onPointerUp, true);
|
|
192
211
|
return () => {
|
|
193
212
|
document.removeEventListener('selectionchange', deferredSync);
|
|
194
213
|
document.removeEventListener('focusout', deferredSync);
|
|
214
|
+
document.removeEventListener('pointerdown', onPointerDown, true);
|
|
215
|
+
document.removeEventListener('pointerup', onPointerUp, true);
|
|
216
|
+
document.removeEventListener('pointercancel', onPointerUp, true);
|
|
195
217
|
// A burst-coalescing rAF may still be in flight at teardown; cancel it so
|
|
196
218
|
// `sync()` never runs against the destroyed component.
|
|
197
219
|
if (pending) cancelAnimationFrame(rafHandle);
|
|
@@ -235,6 +257,7 @@
|
|
|
235
257
|
if (!type) return;
|
|
236
258
|
if (name === 'link') {
|
|
237
259
|
linkValue = hrefInSelection(view.state);
|
|
260
|
+
linkPresent = linkValue.length > 0;
|
|
238
261
|
linkPromptOpen = true;
|
|
239
262
|
return;
|
|
240
263
|
}
|
|
@@ -293,8 +316,14 @@
|
|
|
293
316
|
|
|
294
317
|
<Popover.Root bind:open>
|
|
295
318
|
<Popover.Portal to={portalTarget}>
|
|
319
|
+
<!-- A flip is measured against the root the surface portals into, which is the box
|
|
320
|
+
that clips it where the consumer scrolls one (`.qm-pane`). The default boundary is
|
|
321
|
+
the viewport, which knows nothing of that: a selection on the pane's first visible
|
|
322
|
+
line raises the surface into the room above, where the pane's `overflow` cuts
|
|
323
|
+
it. -->
|
|
296
324
|
<Popover.Content
|
|
297
325
|
customAnchor={anchor ?? null}
|
|
326
|
+
collisionBoundary={portalTarget ?? []}
|
|
298
327
|
side="top"
|
|
299
328
|
align="center"
|
|
300
329
|
trapFocus={false}
|
|
@@ -340,7 +369,7 @@
|
|
|
340
369
|
<button type="submit" class="qm-icon-btn qm-mark-btn">{t.strings.linkApply}</button>
|
|
341
370
|
<!-- Only where there is a link to remove: an arm that does nothing is
|
|
342
371
|
an arm to read past on every other visit to the prompt. -->
|
|
343
|
-
{#if
|
|
372
|
+
{#if linkPresent}
|
|
344
373
|
<button
|
|
345
374
|
type="button"
|
|
346
375
|
class="qm-icon-btn qm-mark-btn"
|
|
@@ -364,7 +393,6 @@
|
|
|
364
393
|
<button
|
|
365
394
|
type="button"
|
|
366
395
|
class="qm-icon-btn qm-mark-btn"
|
|
367
|
-
class:active={activeMarks[m.name]}
|
|
368
396
|
title={m.title}
|
|
369
397
|
aria-label={m.title}
|
|
370
398
|
onmousedown={keepFocus}
|
|
@@ -375,7 +403,6 @@
|
|
|
375
403
|
<button
|
|
376
404
|
type="button"
|
|
377
405
|
class="qm-icon-btn qm-mark-btn"
|
|
378
|
-
class:active={activeMarks.anchor}
|
|
379
406
|
title={t.strings.formatAnchorTitle}
|
|
380
407
|
aria-label={t.strings.formatAnchor}
|
|
381
408
|
onmousedown={keepFocus}
|
|
@@ -408,21 +435,15 @@
|
|
|
408
435
|
gap: var(--_qm-space-half);
|
|
409
436
|
}
|
|
410
437
|
/* Chrome, type, target floor, hover fill and disabled recede come from
|
|
411
|
-
`.qm-icon-btn` (controls.css)
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
438
|
+
`.qm-icon-btn` (controls.css). The ink is stated because the family deliberately
|
|
439
|
+
declares none: its callers disagree, and a popover over content reads at the card's.
|
|
440
|
+
|
|
441
|
+
No state for a mark the selection already carries: a run may carry one over part of
|
|
442
|
+
itself, which a two-state button has no third face for. */
|
|
415
443
|
.qm-mark-btn {
|
|
416
|
-
border: var(--_qm-border-width) solid transparent;
|
|
417
444
|
padding: var(--_qm-space) var(--_qm-space-2);
|
|
418
445
|
color: var(--_qm-ink);
|
|
419
446
|
}
|
|
420
|
-
/* Active is an ink inversion, the one state on this surface a hover fill cannot
|
|
421
|
-
say: a mark already carried is not a mark under the pointer. */
|
|
422
|
-
.qm-mark-btn.active {
|
|
423
|
-
background: var(--_qm-ink);
|
|
424
|
-
color: var(--_qm-surface);
|
|
425
|
-
}
|
|
426
447
|
.qm-link-prompt {
|
|
427
448
|
display: flex;
|
|
428
449
|
gap: var(--_qm-space);
|
|
@@ -167,6 +167,11 @@
|
|
|
167
167
|
.qm-prose-block:not(.qm-control-box) {
|
|
168
168
|
font-size: var(--_qm-text-paper);
|
|
169
169
|
}
|
|
170
|
+
/* Paper's margin, which a field has none of. On the editable, so a press in the strip
|
|
171
|
+
lands a caret; carried by the card's inset it would be dead width. */
|
|
172
|
+
.qm-prose-block:not(.qm-control-box) :global(.ProseMirror) {
|
|
173
|
+
padding-inline: var(--_qm-space-2);
|
|
174
|
+
}
|
|
170
175
|
/* The caret is the body's focus indicator: a ring around the one surface in a card
|
|
171
176
|
that is paper reads as form chrome. So the contenteditable's own outline is dropped
|
|
172
177
|
everywhere, and a boxed leaf takes the shared ring on its wrapper instead
|
|
@@ -95,19 +95,25 @@
|
|
|
95
95
|
/* Attached to `main`, not a block beside it: a square top pulled up by the radius,
|
|
96
96
|
so its background fills the notches `main`'s rounded bottom corners cut and `main`
|
|
97
97
|
paints over the rest. The two read as one block, which is what document-level
|
|
98
|
-
guidance is: the main card's, not a card of its own. Still in-flow, no
|
|
99
|
-
|
|
98
|
+
guidance is: the main card's, not a card of its own. Still in-flow, no lift.
|
|
99
|
+
|
|
100
|
+
It takes the card's hairline, and the tuck makes the two outlines one: the slip's top
|
|
101
|
+
stroke and the tops of its sides run under `main`, so what shows starts at the card's
|
|
102
|
+
bottom corners.
|
|
100
103
|
|
|
101
104
|
The bottom corners take the inner rung where the card above takes the outer: a slip
|
|
102
105
|
is cut crisper than the thing it is tucked under, at every setting of the dial.
|
|
103
106
|
|
|
104
107
|
The insets are asymmetric so the ink is not: the top takes the tuck back, and the
|
|
105
108
|
bottom gives up a rung to the foot's control, whose box stands on the tap floor
|
|
106
|
-
rather than on its own one-line label.
|
|
109
|
+
rather than on its own one-line label. The horizontal is the card's inset plus the
|
|
110
|
+
body's margin (ProseField), so the tip starts under the prose rather than under the
|
|
111
|
+
fields. */
|
|
107
112
|
.qm-tips {
|
|
108
113
|
margin-top: calc(var(--_qm-radius) * -1);
|
|
114
|
+
border: var(--_qm-border-width) solid var(--_qm-border);
|
|
109
115
|
border-radius: 0 0 var(--_qm-radius-inner) var(--_qm-radius-inner);
|
|
110
|
-
padding: calc(var(--_qm-space-3) + var(--_qm-radius)) var(--_qm-space-
|
|
116
|
+
padding: calc(var(--_qm-space-3) + var(--_qm-radius)) var(--_qm-space-5) var(--_qm-space-2);
|
|
111
117
|
background: var(--_qm-tips-surface);
|
|
112
118
|
display: flex;
|
|
113
119
|
flex-direction: column;
|