dsh-fonttune 0.7.0 → 0.7.1
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/CHANGELOG.en.md +13 -0
- package/CHANGELOG.md +13 -0
- package/README.en.md +1 -1
- package/README.md +1 -1
- package/lib/client.js +290 -26
- package/lib/shared.cjs +46 -0
- package/package.json +1 -1
package/CHANGELOG.en.md
CHANGED
|
@@ -2,6 +2,19 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to **dsh-fonttune** are documented here. Chinese version: [CHANGELOG.md](CHANGELOG.md).
|
|
4
4
|
|
|
5
|
+
## [0.7.1] - 2026-10-20
|
|
6
|
+
|
|
7
|
+
### Changed
|
|
8
|
+
- **The panel and the dot each decide their own side**: the panel opens into the quadrant its own middle sits in, while the dot still snaps to the corner it remembers, so the two no longer share one rule.
|
|
9
|
+
- **Switching corners takes less travel**: the conversation's middle is the line to cross, so the corner follows the panel's own middle rather than the region's edge.
|
|
10
|
+
- **Snapping is one continuous motion**: after a release the panel and the dot travel to the target corner together, instead of the panel jumping to the other side first and then sliding.
|
|
11
|
+
|
|
12
|
+
### Fixed
|
|
13
|
+
- **The panel sometimes rested on the window's bottom-left corner**: while the conversation was not measurable yet, the dot was moved onto the window corner and that position was stored; it now moves only against a measurable conversation and never stores a stand-in.
|
|
14
|
+
- **The dot sat on the sidebar in a narrow window**: a conversation squeezed to a sliver is still the region, instead of falling back to the whole window.
|
|
15
|
+
- **The shadow did not follow the unfold**: the panel's shadow now changes with the shape while it unfolds and folds.
|
|
16
|
+
- **The first frame of an unfold flickered**: the panel no longer blinks before it appears.
|
|
17
|
+
|
|
5
18
|
## [0.7.0] - 2026-10-19
|
|
6
19
|
|
|
7
20
|
### Changed
|
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,19 @@
|
|
|
2
2
|
|
|
3
3
|
**dsh-fonttune** 的重要变更都记录在这里。英文版见 [CHANGELOG.en.md](CHANGELOG.en.md)。
|
|
4
4
|
|
|
5
|
+
## [0.7.1] - 2026-10-20
|
|
6
|
+
|
|
7
|
+
### 变更
|
|
8
|
+
- **面板与圆点各自决定吸附方向**:面板按自己的中心落在对话区的哪个象限展开,圆点仍吸附到自己记住的角落,两者不再共用一套判断。
|
|
9
|
+
- **换角所需移动更少**:以对话区中心为原点,面板中心越过中线即可换到对应的角,不必先拖到区域边缘。
|
|
10
|
+
- **吸附是一个连续动作**:换角松手后,面板与圆点一同滑向目标角落,面板不再先跳到另一侧再飞过去。
|
|
11
|
+
|
|
12
|
+
### 修复
|
|
13
|
+
- **悬浮面板偶尔停在窗口左下角**:对话区尚未完成布局的瞬间,圆点会被移到窗口角落并记住那个位置;现在只在对话区可测量时移动,也不再保存这种临时位置。
|
|
14
|
+
- **窗口很窄时圆点落在侧边栏上**:对话区被挤成一条窄边时仍按对话区吸附,不再退回整块窗口。
|
|
15
|
+
- **展开与收起时投影不同步**:面板的投影现在随展开、收起的形状一同变化。
|
|
16
|
+
- **展开的第一帧闪动**:面板展开时不再先闪一下再出现。
|
|
17
|
+
|
|
5
18
|
## [0.7.0] - 2026-10-19
|
|
6
19
|
|
|
7
20
|
### 变更
|
package/README.en.md
CHANGED
|
@@ -39,7 +39,7 @@ The floating tuning panel (left, with its "in force right now" readout) and the
|
|
|
39
39
|
|
|
40
40
|
| Plugin version | Supported DSH versions |
|
|
41
41
|
| --- | --- |
|
|
42
|
-
| **0.7.x** (latest, 0.7.
|
|
42
|
+
| **0.7.x** (latest, 0.7.1) | 0.1.7-alpha.1, 0.1.7-alpha.2, 0.1.7-rc.1, 0.1.7-rc.2, 0.2.0-rc.1, 0.2.0-rc.2, 0.2.1-alpha.1, 0.2.1-alpha.2, 0.2.1-alpha.3, 0.2.1-alpha.4, 0.2.1-alpha.5, 0.2.1-rc.1, 0.2.1-rc.2 |
|
|
43
43
|
| 0.6.x (0.6.2) | 0.1.7-alpha.1, 0.1.7-alpha.2, 0.1.7-rc.1, 0.1.7-rc.2, 0.2.0-rc.1, 0.2.0-rc.2, 0.2.1-alpha.1, 0.2.1-alpha.2, 0.2.1-alpha.3, 0.2.1-alpha.4, 0.2.1-alpha.5, 0.2.1-rc.1, 0.2.1-rc.2 |
|
|
44
44
|
| 0.5.x (0.5.0) | 0.1.7-alpha.1, 0.1.7-alpha.2, 0.1.7-rc.1, 0.1.7-rc.2, 0.2.0-rc.1, 0.2.0-rc.2, 0.2.1-alpha.1, 0.2.1-alpha.2 |
|
|
45
45
|
| 0.4.x (0.4.2) | 0.1.7-alpha.1, 0.1.7-alpha.2, 0.1.7-rc.1, 0.1.7-rc.2, 0.2.0-rc.1, 0.2.0-rc.2, 0.2.1-alpha.1, 0.2.1-alpha.2 |
|
package/README.md
CHANGED
|
@@ -39,7 +39,7 @@
|
|
|
39
39
|
|
|
40
40
|
| 插件版本 | 支持的 DSH 版本 |
|
|
41
41
|
| --- | --- |
|
|
42
|
-
| **0.7.x**(最新,0.7.
|
|
42
|
+
| **0.7.x**(最新,0.7.1) | 0.1.7-alpha.1、0.1.7-alpha.2、0.1.7-rc.1、0.1.7-rc.2、0.2.0-rc.1、0.2.0-rc.2、0.2.1-alpha.1、0.2.1-alpha.2、0.2.1-alpha.3、0.2.1-alpha.4、0.2.1-alpha.5、0.2.1-rc.1、0.2.1-rc.2 |
|
|
43
43
|
| 0.6.x(0.6.2) | 0.1.7-alpha.1、0.1.7-alpha.2、0.1.7-rc.1、0.1.7-rc.2、0.2.0-rc.1、0.2.0-rc.2、0.2.1-alpha.1、0.2.1-alpha.2、0.2.1-alpha.3、0.2.1-alpha.4、0.2.1-alpha.5、0.2.1-rc.1、0.2.1-rc.2 |
|
|
44
44
|
| 0.5.x(0.5.0) | 0.1.7-alpha.1、0.1.7-alpha.2、0.1.7-rc.1、0.1.7-rc.2、0.2.0-rc.1、0.2.0-rc.2、0.2.1-alpha.1、0.2.1-alpha.2 |
|
|
45
45
|
| 0.4.x(0.4.2) | 0.1.7-alpha.1、0.1.7-alpha.2、0.1.7-rc.1、0.1.7-rc.2、0.2.0-rc.1、0.2.0-rc.2、0.2.1-alpha.1、0.2.1-alpha.2 |
|
package/lib/client.js
CHANGED
|
@@ -1194,6 +1194,15 @@ function floatBoundsPick(rects, viewport) {
|
|
|
1194
1194
|
if (!isFinite(vh) || vh <= 0) vh = FLOAT_VIEWPORT_H;
|
|
1195
1195
|
var full = { left: 0, top: 0, right: vw, bottom: vh, width: vw, height: vh };
|
|
1196
1196
|
var list = Array.isArray(rects) ? rects : [];
|
|
1197
|
+
// A region narrower than one dot used to count as "no region at all", and the
|
|
1198
|
+
// fallback was the WHOLE viewport — so in a narrow window the dot parked on the
|
|
1199
|
+
// window's own corner, over the sidebar, instead of on the conversation's. The
|
|
1200
|
+
// largest readable box is kept as a fallback, and it names itself in `index` so
|
|
1201
|
+
// the watcher holds the box whose size actually defines the region. `full` is
|
|
1202
|
+
// left for a page with no region at all — or only degenerate 0-area rects.
|
|
1203
|
+
var best = null;
|
|
1204
|
+
var bestIndex = -1;
|
|
1205
|
+
var bestArea = -1;
|
|
1197
1206
|
for (var index = 0; index < list.length; index += 1) {
|
|
1198
1207
|
var box = floatBoxOf(list[index]);
|
|
1199
1208
|
if (box === null) continue;
|
|
@@ -1203,12 +1212,18 @@ function floatBoundsPick(rects, viewport) {
|
|
|
1203
1212
|
var bottom = Math.max(0, Math.min(vh, box.bottom));
|
|
1204
1213
|
var width = right - left;
|
|
1205
1214
|
var height = bottom - top;
|
|
1215
|
+
if (width > 0 && height > 0 && width * height > bestArea) {
|
|
1216
|
+
bestArea = width * height;
|
|
1217
|
+
bestIndex = index;
|
|
1218
|
+
best = { left: left, top: top, right: right, bottom: bottom, width: width, height: height };
|
|
1219
|
+
}
|
|
1206
1220
|
if (width < FLOAT_BOUNDS_MIN || height < FLOAT_BOUNDS_MIN) continue;
|
|
1207
1221
|
return {
|
|
1208
1222
|
index: index,
|
|
1209
1223
|
bounds: { left: left, top: top, right: right, bottom: bottom, width: width, height: height },
|
|
1210
1224
|
};
|
|
1211
1225
|
}
|
|
1226
|
+
if (best !== null) return { index: bestIndex, bounds: best };
|
|
1212
1227
|
return { index: -1, bounds: full };
|
|
1213
1228
|
}
|
|
1214
1229
|
|
|
@@ -1327,6 +1342,36 @@ function floatTransformOrigin(quadrant) {
|
|
|
1327
1342
|
return "100% 100%";
|
|
1328
1343
|
}
|
|
1329
1344
|
|
|
1345
|
+
/**
|
|
1346
|
+
* Which side the open CARD grows towards: the one with more room for it.
|
|
1347
|
+
*
|
|
1348
|
+
* The card hangs off the dot, so it can only extend one way from it, and the
|
|
1349
|
+
* side that holds more of the card is the side the conversation has room for.
|
|
1350
|
+
* Each axis compares the room beyond the dot's own box on either side and sends
|
|
1351
|
+
* the card to the larger one — which is also the side its middle ends up on, so
|
|
1352
|
+
* "the panel goes to the corner it sits in" holds by construction.
|
|
1353
|
+
*
|
|
1354
|
+
* This is the CARD's rule, not the dot's: the dot keeps the corner it remembers
|
|
1355
|
+
* living in (see `floatCornerPoint`), while a rule shared by both is what made
|
|
1356
|
+
* the card jump whenever the dot crossed a line the card had not. The answer
|
|
1357
|
+
* depends on the dot and the region and on nothing else — never on a card it
|
|
1358
|
+
* just placed — so it cannot vote itself back and forth.
|
|
1359
|
+
* @param {unknown} bounds - the region, as `floatBoundsFrom` returns it.
|
|
1360
|
+
* @param {number} x - the dot's left edge, in CSS pixels.
|
|
1361
|
+
* @param {number} y - the dot's top edge, in CSS pixels.
|
|
1362
|
+
* @param {number} [dot] - the dot's diameter (defaults to FLOAT_DOT).
|
|
1363
|
+
* @returns {string} one of "tl", "tr", "bl", "br" — the quadrant the card is
|
|
1364
|
+
* anchored on, which is the corner it grows away from.
|
|
1365
|
+
*/
|
|
1366
|
+
function floatCardCorner(bounds, x, y, dot) {
|
|
1367
|
+
var box = floatBoxOf(bounds);
|
|
1368
|
+
if (box === null) box = { left: 0, top: 0, right: FLOAT_VIEWPORT_W, bottom: FLOAT_VIEWPORT_H };
|
|
1369
|
+
var size = dot === undefined ? FLOAT_DOT : dot;
|
|
1370
|
+
var growRight = box.right - x >= x + size - box.left;
|
|
1371
|
+
var growDown = box.bottom - y >= y + size - box.top;
|
|
1372
|
+
return (growDown ? "t" : "b") + (growRight ? "l" : "r");
|
|
1373
|
+
}
|
|
1374
|
+
|
|
1330
1375
|
/**
|
|
1331
1376
|
* Which corner of the card lands on the dot, and where the expansion starts.
|
|
1332
1377
|
*
|
|
@@ -4623,6 +4668,7 @@ var shared = {
|
|
|
4623
4668
|
floatExpandDirection: floatExpandDirection,
|
|
4624
4669
|
floatTransformOrigin: floatTransformOrigin,
|
|
4625
4670
|
floatCardAnchor: floatCardAnchor,
|
|
4671
|
+
floatCardCorner: floatCardCorner,
|
|
4626
4672
|
floatClipStart: floatClipStart,
|
|
4627
4673
|
floatClipRest: floatClipRest,
|
|
4628
4674
|
isRestClip: isRestClip,
|
|
@@ -10377,7 +10423,10 @@ var GLASS_OK = (function () {
|
|
|
10377
10423
|
|
|
10378
10424
|
function mountFloatPanel(scope, t, hostCtx) {
|
|
10379
10425
|
/** How far the panel's drop shadow reaches past its own box (10px offset + blur). */
|
|
10380
|
-
var SHADOW_REACH = 56;
|
|
10426
|
+
var SHADOW_REACH = 56;
|
|
10427
|
+
/** The dot's own snap slide, in ms — the stylesheet's `.dfp-floatSnap` transition. */
|
|
10428
|
+
var FLOAT_SNAP_MS = 160;
|
|
10429
|
+
var noop = function () {};
|
|
10381
10430
|
if (typeof document === "undefined" || !document.body) return { destroy: noop, toggle: noop };
|
|
10382
10431
|
// The offline stand-ins hand out element stubs without listeners or
|
|
10383
10432
|
// selectors; without a real element face there is nothing to mount on.
|
|
@@ -10468,6 +10517,19 @@ function mountFloatPanel(scope, t, hostCtx) {
|
|
|
10468
10517
|
// end on the same small rectangle the unfold started from, and the card's own
|
|
10469
10518
|
// measurement only exists while it is mounted.
|
|
10470
10519
|
var liveQuadrant = null;
|
|
10520
|
+
// The CARD's own side, re-decided when the panel opens (and when the dot is
|
|
10521
|
+
// dropped or the region changes shape). It is NOT the dot's corner: the card
|
|
10522
|
+
// goes wherever it has the most room, while the dot keeps the corner it
|
|
10523
|
+
// remembers living in. One rule shared by both is what made the card jump
|
|
10524
|
+
// whenever the dot crossed a line the card had not.
|
|
10525
|
+
var cardQuadrant = null;
|
|
10526
|
+
// Whether the NEXT render follows a snap. The card then keeps the place it
|
|
10527
|
+
// already had on screen and TRAVELS to the new corner with the dot, instead of
|
|
10528
|
+
// jumping a panel-width to the other side of it (see the glide in `render`).
|
|
10529
|
+
var anchorGlide = false;
|
|
10530
|
+
// The card's own offset inside the host, as laid out last time: the glide is
|
|
10531
|
+
// the difference between where the new side puts it and that.
|
|
10532
|
+
var lastAnchor = null;
|
|
10471
10533
|
|
|
10472
10534
|
var el = function (tag, className, text) {
|
|
10473
10535
|
var node = document.createElement(tag);
|
|
@@ -10846,6 +10908,11 @@ function mountFloatPanel(scope, t, hostCtx) {
|
|
|
10846
10908
|
return null;
|
|
10847
10909
|
};
|
|
10848
10910
|
|
|
10911
|
+
// Whether the last region reading was a real region or the whole-window
|
|
10912
|
+
// stand-in (see `floatBounds`). A stand-in never moves the dot: it means "no
|
|
10913
|
+
// conversation to measure right now", not "the conversation is the window".
|
|
10914
|
+
var boundsAreReal = false;
|
|
10915
|
+
|
|
10849
10916
|
/** The window size the bounds are cut to, with the shared fallback. */
|
|
10850
10917
|
var floatViewport = function () {
|
|
10851
10918
|
return {
|
|
@@ -10975,10 +11042,19 @@ function mountFloatPanel(scope, t, hostCtx) {
|
|
|
10975
11042
|
* The conversation when it can be found, the whole window when it cannot (a
|
|
10976
11043
|
* settings page has no prose at all): the window is what this used to be
|
|
10977
11044
|
* everywhere, and it stays the last resort rather than the rule.
|
|
11045
|
+
*
|
|
11046
|
+
* WHICH one answered is remembered in `boundsAreReal`, because the two are not
|
|
11047
|
+
* interchangeable for a MOVE: the whole window is a stand-in for "there is no
|
|
11048
|
+
* conversation to measure at this instant" — a session still mounting, a
|
|
11049
|
+
* message list laid out at 0×0 — and a dot parked on the window's corner over
|
|
11050
|
+
* a page that does have a conversation is exactly the reported "it sits on the
|
|
11051
|
+
* window's left edge instead of the conversation's corner".
|
|
10978
11052
|
* @returns {object} the bounds, in viewport coordinates.
|
|
10979
11053
|
*/
|
|
10980
11054
|
var floatBounds = function () {
|
|
10981
|
-
|
|
11055
|
+
var picked = shared.floatBoundsPick(floatBoundsRects(), floatViewport());
|
|
11056
|
+
boundsAreReal = picked.index >= 0;
|
|
11057
|
+
return picked.bounds;
|
|
10982
11058
|
};
|
|
10983
11059
|
|
|
10984
11060
|
/**
|
|
@@ -11067,6 +11143,36 @@ function mountFloatPanel(scope, t, hostCtx) {
|
|
|
11067
11143
|
);
|
|
11068
11144
|
};
|
|
11069
11145
|
|
|
11146
|
+
/**
|
|
11147
|
+
* The shade's scale for the panel's CURRENT visible box.
|
|
11148
|
+
*
|
|
11149
|
+
* Read from the card's own clip every frame, not driven by a CSS transition: the
|
|
11150
|
+
* small value only exists for the two arming frames, and a transition beginning
|
|
11151
|
+
* there is cleared before it can run (measured: the inline value was gone and
|
|
11152
|
+
* `transform` was `none` 138 ms into the unfold, with the shadow at full size the
|
|
11153
|
+
* whole way). Written per frame, the shadow is exactly as big as what the panel
|
|
11154
|
+
* is showing, on every frame, by construction.
|
|
11155
|
+
* @param {object} card - the live card.
|
|
11156
|
+
* @param {string} clip - its computed clip-path.
|
|
11157
|
+
* @returns {string} the inline transform, or "" for the resting panel.
|
|
11158
|
+
*/
|
|
11159
|
+
var shadeFromClip = function (card, clip) {
|
|
11160
|
+
if (clip === "none") return "";
|
|
11161
|
+
var found = /^inset\((-?[\d.]+)px (-?[\d.]+)px (-?[\d.]+)px (-?[\d.]+)px/.exec(clip);
|
|
11162
|
+
if (found === null) return "";
|
|
11163
|
+
var width = 0;
|
|
11164
|
+
var height = 0;
|
|
11165
|
+
try {
|
|
11166
|
+
width = card.offsetWidth;
|
|
11167
|
+
height = card.offsetHeight;
|
|
11168
|
+
} catch (error) {
|
|
11169
|
+
return "";
|
|
11170
|
+
}
|
|
11171
|
+
if (!(width > 0) || !(height > 0)) return "";
|
|
11172
|
+
var visibleW = Math.max(1, width - Number(found[2]) - Number(found[4]));
|
|
11173
|
+
var visibleH = Math.max(1, height - Number(found[1]) - Number(found[3]));
|
|
11174
|
+
return "scale(" + (visibleW / width).toFixed(4) + ", " + (visibleH / height).toFixed(4) + ")";
|
|
11175
|
+
};
|
|
11070
11176
|
var mirrorGlassClip = function (card, glass, ms) { if (glass === null || glass === undefined) return;
|
|
11071
11177
|
var shade = host.querySelector(".dfp-floatShade");
|
|
11072
11178
|
var until = Date.now() + ms;
|
|
@@ -11079,11 +11185,11 @@ function mountFloatPanel(scope, t, hostCtx) {
|
|
|
11079
11185
|
return;
|
|
11080
11186
|
}
|
|
11081
11187
|
glass.style.clipPath = clip;
|
|
11082
|
-
|
|
11083
|
-
//
|
|
11084
|
-
//
|
|
11085
|
-
//
|
|
11086
|
-
if (shade !== null && shade !== undefined) shade.style.
|
|
11188
|
+
// The shade is SCALED to the panel's visible box, frame by frame (see
|
|
11189
|
+
// `shadeFromClip`): its shadow is painted from its own box, so a layer left at
|
|
11190
|
+
// the full panel size shows a full-size shadow for the whole unfold — the
|
|
11191
|
+
// "second edge" beside the panel's own. Scaling the layer scales the shadow.
|
|
11192
|
+
if (shade !== null && shade !== undefined) shade.style.transform = shadeFromClip(card, clip);
|
|
11087
11193
|
if (Date.now() < until) globalThis.requestAnimationFrame(step);
|
|
11088
11194
|
};
|
|
11089
11195
|
if (typeof globalThis.requestAnimationFrame === "function") globalThis.requestAnimationFrame(step);
|
|
@@ -11430,6 +11536,56 @@ function mountFloatPanel(scope, t, hostCtx) {
|
|
|
11430
11536
|
return fallback;
|
|
11431
11537
|
};
|
|
11432
11538
|
|
|
11539
|
+
/**
|
|
11540
|
+
* The middle of the CARD as it is on the page, or null while it is not.
|
|
11541
|
+
*
|
|
11542
|
+
* Read from the layer the card sits in (the wrapper carries the anchored edge
|
|
11543
|
+
* and the size), so the unfold's own scale — which rides the card inside it —
|
|
11544
|
+
* cannot make a growing panel look like a moving one.
|
|
11545
|
+
* @returns {{x: number, y: number}|null} its middle in viewport coordinates.
|
|
11546
|
+
*/
|
|
11547
|
+
var cardMiddle = function () {
|
|
11548
|
+
var layer = null;
|
|
11549
|
+
try {
|
|
11550
|
+
layer = host.querySelector(".dfp-floatShadow");
|
|
11551
|
+
} catch (error) {
|
|
11552
|
+
layer = null;
|
|
11553
|
+
}
|
|
11554
|
+
if (layer === null || layer === undefined) return null;
|
|
11555
|
+
var rect = rectOf(layer);
|
|
11556
|
+
if (rect === null || !(rect.width > 0) || !(rect.height > 0)) return null;
|
|
11557
|
+
return { x: rect.left + rect.width / 2, y: rect.top + rect.height / 2 };
|
|
11558
|
+
};
|
|
11559
|
+
|
|
11560
|
+
/**
|
|
11561
|
+
* Re-decide which side of the conversation the card opens towards.
|
|
11562
|
+
*
|
|
11563
|
+
* The rule is the card's OWN, and it is about ROOM: the card hangs off the
|
|
11564
|
+
* dot, so it can only grow one way from it, and the side that holds more of it
|
|
11565
|
+
* is the side the conversation has room for. Per axis the two rooms on either
|
|
11566
|
+
* side of the dot are compared and the card is sent to the larger one, which
|
|
11567
|
+
* is also the side its middle ends up on — the panel flies to the corner it
|
|
11568
|
+
* sits in, measured against the conversation rather than against the dot.
|
|
11569
|
+
*
|
|
11570
|
+
* The dot keeps its own rule — the corner it remembers living in — and that
|
|
11571
|
+
* difference is the point: a rule shared by both made the card jump whenever
|
|
11572
|
+
* the dot crossed a line the card had not. This answer depends on the dot and
|
|
11573
|
+
* the region and on nothing else, so it cannot vote itself back and forth.
|
|
11574
|
+
*
|
|
11575
|
+
* Called at the moments a side can legitimately change — an open, when the
|
|
11576
|
+
* card does not exist yet, a release that lands the dot on another corner, and
|
|
11577
|
+
* a region that changed shape — and never from `render`. A caller that changes
|
|
11578
|
+
* the side must also place the card WITHOUT a slide: a side is an anchored
|
|
11579
|
+
* edge, so the card moves by its own width when it changes.
|
|
11580
|
+
* @param {object} [bounds] - the region, when the caller has it already.
|
|
11581
|
+
*/
|
|
11582
|
+
var voteCardQuadrant = function (bounds, at) {
|
|
11583
|
+
var region = bounds === undefined ? floatBounds() : bounds;
|
|
11584
|
+
var origin = at === undefined ? pos : at;
|
|
11585
|
+
if (origin === null || origin === undefined) return;
|
|
11586
|
+
cardQuadrant = shared.floatCardCorner(region, origin.x, origin.y);
|
|
11587
|
+
};
|
|
11588
|
+
|
|
11433
11589
|
/**
|
|
11434
11590
|
* Snap the dot to the corner it LIVES in, with a short slide.
|
|
11435
11591
|
*
|
|
@@ -11452,8 +11608,15 @@ function mountFloatPanel(scope, t, hostCtx) {
|
|
|
11452
11608
|
* to send the dot to the top-left corner the user never asked for.
|
|
11453
11609
|
* @returns {{x: number, y: number}} the corner the dot now sits on.
|
|
11454
11610
|
*/
|
|
11455
|
-
var snapToCorner = function () {
|
|
11611
|
+
var snapToCorner = function (instant) {
|
|
11456
11612
|
var bounds = floatBounds();
|
|
11613
|
+
// A stand-in region is not a home: the window's corner is what the dot would
|
|
11614
|
+
// land on over a page whose conversation is merely not measurable yet, which
|
|
11615
|
+
// is the reported "it snaps to the window's left edge instead of the
|
|
11616
|
+
// conversation's corner". A dot the document already placed keeps the corner
|
|
11617
|
+
// the last REAL region gave it; the observer settles it again the moment one
|
|
11618
|
+
// exists (see `reflowIntoBounds`).
|
|
11619
|
+
if (!boundsAreReal && pos !== null) return pos;
|
|
11457
11620
|
var snapped;
|
|
11458
11621
|
if (pos !== null) {
|
|
11459
11622
|
snapped = cornerHome(bounds, pos);
|
|
@@ -11475,7 +11638,11 @@ function mountFloatPanel(scope, t, hostCtx) {
|
|
|
11475
11638
|
}
|
|
11476
11639
|
pos = snapped;
|
|
11477
11640
|
try {
|
|
11478
|
-
|
|
11641
|
+
// An instant settle must also DROP a snap class left over from an earlier
|
|
11642
|
+
// move, or the very same `left`/`top` write is animated and the panel appears
|
|
11643
|
+
// to drift over from where it used to be.
|
|
11644
|
+
if (instant === true) host.classList.remove("dfp-floatSnap");
|
|
11645
|
+
else host.classList.add("dfp-floatSnap");
|
|
11479
11646
|
} catch (error) {
|
|
11480
11647
|
// A host without classes still lands on the snapped corner.
|
|
11481
11648
|
}
|
|
@@ -11586,9 +11753,15 @@ function mountFloatPanel(scope, t, hostCtx) {
|
|
|
11586
11753
|
* dot rather than the document handing it a position.
|
|
11587
11754
|
* @returns {boolean} true when the dot was moved onto its corner.
|
|
11588
11755
|
*/
|
|
11589
|
-
var settleOnCorner = function (regionMoved) {
|
|
11756
|
+
var settleOnCorner = function (regionMoved, instant) {
|
|
11590
11757
|
if (drag !== null || resize !== null) return false;
|
|
11591
11758
|
var bounds = floatBounds();
|
|
11759
|
+
// Nothing real to settle onto: a shut panel on a settings page, or a
|
|
11760
|
+
// conversation that is not measurable at this instant, leaves the dot where
|
|
11761
|
+
// it is — moving it onto the window's corner is the bug, not the fallback.
|
|
11762
|
+
// With nothing stored at all there is no home to keep, so the stylesheet's
|
|
11763
|
+
// own corner is pinned as before.
|
|
11764
|
+
if (!boundsAreReal && pos !== null) return false;
|
|
11592
11765
|
var at = pos;
|
|
11593
11766
|
if (at === null) {
|
|
11594
11767
|
var rect = rectOf(host);
|
|
@@ -11628,7 +11801,7 @@ function mountFloatPanel(scope, t, hostCtx) {
|
|
|
11628
11801
|
at.y + shared.FLOAT_DOT <= bounds.bottom + 0.5
|
|
11629
11802
|
);
|
|
11630
11803
|
pos = { x: Math.round(at.x), y: Math.round(at.y) };
|
|
11631
|
-
snapToCorner();
|
|
11804
|
+
snapToCorner(instant);
|
|
11632
11805
|
// Both fields in ONE write: the dot is on the corner and the document is
|
|
11633
11806
|
// told which corner that is, in the same `mutate`. A region that changed
|
|
11634
11807
|
// shape is the caller that has to store the move it just made, wherever the
|
|
@@ -11653,8 +11826,22 @@ function mountFloatPanel(scope, t, hostCtx) {
|
|
|
11653
11826
|
* `settleOnCorner`).
|
|
11654
11827
|
*/
|
|
11655
11828
|
var reflowIntoBounds = function () {
|
|
11656
|
-
|
|
11657
|
-
|
|
11829
|
+
var bounds = floatBounds();
|
|
11830
|
+
var wasSide = cardQuadrant;
|
|
11831
|
+
if (pos !== null) {
|
|
11832
|
+
// The card's side belongs to the corner the dot is going to, so it is
|
|
11833
|
+
// decided BEFORE the dot is moved — the two then land in the same frame,
|
|
11834
|
+
// and a changed side never sweeps across the middle behind a slide.
|
|
11835
|
+
var home = cornerHome(bounds, pos);
|
|
11836
|
+
cardQuadrant = shared.floatCardCorner(bounds, home.x, home.y);
|
|
11837
|
+
}
|
|
11838
|
+
var moved = settleOnCorner(true);
|
|
11839
|
+
if (open && (moved || cardQuadrant !== wasSide)) {
|
|
11840
|
+
// The region moved the dot and possibly the card's side with it: follow it
|
|
11841
|
+
// in one motion rather than in a jump and a slide (see `anchorGlide`).
|
|
11842
|
+
anchorGlide = moved;
|
|
11843
|
+
render();
|
|
11844
|
+
}
|
|
11658
11845
|
};
|
|
11659
11846
|
|
|
11660
11847
|
/**
|
|
@@ -11948,7 +12135,10 @@ function mountFloatPanel(scope, t, hostCtx) {
|
|
|
11948
12135
|
// browser cancelled) would otherwise unfold the card in the middle of the
|
|
11949
12136
|
// conversation. An ordinary open — the dot already on its corner — moves
|
|
11950
12137
|
// nothing at all.
|
|
11951
|
-
settleOnCorner();
|
|
12138
|
+
settleOnCorner(false, true);
|
|
12139
|
+
// Which side the unfold goes to: the card's own vote, taken once, here —
|
|
12140
|
+
// the one moment before it exists on the page (see `voteCardQuadrant`).
|
|
12141
|
+
voteCardQuadrant();
|
|
11952
12142
|
playEnter = true;
|
|
11953
12143
|
if (closeTimer !== null) {
|
|
11954
12144
|
globalThis.clearTimeout(closeTimer);
|
|
@@ -12242,10 +12432,35 @@ function mountFloatPanel(scope, t, hostCtx) {
|
|
|
12242
12432
|
// region is where it lives from now on. Every other path reads the
|
|
12243
12433
|
// remembered corner instead (see `settleOnCorner`), which is what keeps a
|
|
12244
12434
|
// region change from re-deciding it from stale coordinates.
|
|
12245
|
-
|
|
12435
|
+
var bounds = floatBounds();
|
|
12436
|
+
// Which corner the dot goes to is read from the PANEL's own middle while
|
|
12437
|
+
// the panel is up — a panel dragged by its head hangs a full card away
|
|
12438
|
+
// from the dot, so asking where the DOT is made the user haul it far past
|
|
12439
|
+
// the conversation's own middle to reach the far side. The conversation's
|
|
12440
|
+
// middle is the origin either way: above it is up, right of it is right.
|
|
12441
|
+
var ref = cardMiddle();
|
|
12442
|
+
if (ref === null) {
|
|
12443
|
+
ref = { x: pos.x + shared.FLOAT_DOT / 2, y: pos.y + shared.FLOAT_DOT / 2 };
|
|
12444
|
+
}
|
|
12445
|
+
corner = shared.floatQuadrantIn(bounds, ref.x, ref.y);
|
|
12446
|
+
// The card's side is decided for the corner the dot LANDS on, not for
|
|
12447
|
+
// where the hand let go: a dot moved to the opposite corner has to take
|
|
12448
|
+
// the card with it, or the panel hangs off the conversation on the side
|
|
12449
|
+
// the old corner had room on.
|
|
12450
|
+
var home = shared.floatCornerPoint(bounds, corner);
|
|
12451
|
+
// Voted for the corner the dot is LANDING on, without moving the dot
|
|
12452
|
+
// first: a stand-in region refuses to move the dot, and a `pos` written
|
|
12453
|
+
// here would then claim a corner the host never went to.
|
|
12454
|
+
voteCardQuadrant(bounds, home);
|
|
12455
|
+
// The snap itself is always animated. The card re-anchors in the same
|
|
12456
|
+
// frame, so a side that changed is already on its new edge while the dot
|
|
12457
|
+
// slides — one reposition at the release point rather than a card dragged
|
|
12458
|
+
// across the screen with its far edge pinned.
|
|
12246
12459
|
snapToCorner();
|
|
12247
12460
|
var live = readState();
|
|
12248
12461
|
writeLivePos(live);
|
|
12462
|
+
// The rebuild that follows rides the snap: a side that changed glides.
|
|
12463
|
+
anchorGlide = open;
|
|
12249
12464
|
render();
|
|
12250
12465
|
};
|
|
12251
12466
|
var up = function () {
|
|
@@ -12457,7 +12672,12 @@ function mountFloatPanel(scope, t, hostCtx) {
|
|
|
12457
12672
|
if (open && !(playEnter && !motionReduced()) && typeof dot.classList !== "undefined") {
|
|
12458
12673
|
dot.classList.add("dfp-dotAway");
|
|
12459
12674
|
}
|
|
12460
|
-
if (!open)
|
|
12675
|
+
if (!open) {
|
|
12676
|
+
// A shut panel has no card to glide; a glide armed by a snap is spent here
|
|
12677
|
+
// rather than left to surprise the next open.
|
|
12678
|
+
anchorGlide = false;
|
|
12679
|
+
return;
|
|
12680
|
+
}
|
|
12461
12681
|
|
|
12462
12682
|
// The shadow's own layer: the card is clipped while it animates, and a clip
|
|
12463
12683
|
// on the card would clip the card's own filter away with it (see the sheet).
|
|
@@ -12483,15 +12703,24 @@ function mountFloatPanel(scope, t, hostCtx) {
|
|
|
12483
12703
|
// first one is a decision, not a coincidence.
|
|
12484
12704
|
anchor = { x: bounds.right, y: bounds.bottom };
|
|
12485
12705
|
}
|
|
12486
|
-
var quadrant
|
|
12487
|
-
|
|
12488
|
-
|
|
12489
|
-
|
|
12490
|
-
|
|
12491
|
-
|
|
12492
|
-
|
|
12493
|
-
|
|
12494
|
-
|
|
12706
|
+
var quadrant;
|
|
12707
|
+
if (cardQuadrant !== null) {
|
|
12708
|
+
// The card's side is its own answer (see `voteCardQuadrant`): the side of
|
|
12709
|
+
// the dot that holds more of the card, voted when a side may change.
|
|
12710
|
+
quadrant = cardQuadrant;
|
|
12711
|
+
} else {
|
|
12712
|
+
// A card that has never been placed anywhere unfolds out of the dot, which
|
|
12713
|
+
// is also where a first panel ends up.
|
|
12714
|
+
quadrant = shared.floatQuadrantIn(bounds, anchor.x, anchor.y);
|
|
12715
|
+
// The remembered corner is the answer the card is really anchored on: a
|
|
12716
|
+
// region narrow enough puts a corner's coordinates on the other side of its
|
|
12717
|
+
// own middle, and the card still has to grow AWAY from the corner the dot
|
|
12718
|
+
// lives in rather than flip around it. A dot that is not on its remembered
|
|
12719
|
+
// corner (mid-drag, or a position just adopted) is read from where it is.
|
|
12720
|
+
// Asked through a helper: `corner` below is the CARD's anchored corner, and
|
|
12721
|
+
// a local of that name here would shadow the dot's identity.
|
|
12722
|
+
quadrant = dotQuadrant(bounds, anchor, quadrant);
|
|
12723
|
+
}
|
|
12495
12724
|
liveQuadrant = quadrant;
|
|
12496
12725
|
var expand = shared.floatExpandDirection(quadrant);
|
|
12497
12726
|
try {
|
|
@@ -12822,10 +13051,45 @@ function mountFloatPanel(scope, t, hostCtx) {
|
|
|
12822
13051
|
});
|
|
12823
13052
|
card.appendChild(grip);
|
|
12824
13053
|
})();
|
|
12825
|
-
|
|
13054
|
+
var shadeEl = el("div", "dfp-floatShade");
|
|
13055
|
+
shadeEl.style.transformOrigin = cardCorner.origin;
|
|
13056
|
+
shadow.appendChild(shadeEl);
|
|
12826
13057
|
shadow.appendChild(glass);
|
|
12827
13058
|
shadow.appendChild(card);
|
|
12828
13059
|
host.appendChild(shadow);
|
|
13060
|
+
// A side is an anchored EDGE, so switching it moves the card by its own
|
|
13061
|
+
// width in one frame. On a snap that would be a jump to the middle of the
|
|
13062
|
+
// screen FOLLOWED by the slide to the corner — two motions. The card is put
|
|
13063
|
+
// back where it already was on screen with a translate, and that translate
|
|
13064
|
+
// is released over the dot's own 160 ms: the panel then travels from where
|
|
13065
|
+
// the user let go straight to the corner, in one motion, with the dot.
|
|
13066
|
+
var anchorLeft = 0;
|
|
13067
|
+
var anchorTop = 0;
|
|
13068
|
+
try {
|
|
13069
|
+
anchorLeft = shadow.offsetLeft;
|
|
13070
|
+
anchorTop = shadow.offsetTop;
|
|
13071
|
+
} catch (error) {
|
|
13072
|
+
// An unmeasurable layer keeps no glide; the side change is instant.
|
|
13073
|
+
}
|
|
13074
|
+
if (anchorGlide && lastAnchor !== null) {
|
|
13075
|
+
anchorGlide = false;
|
|
13076
|
+
var glideX = lastAnchor.left - anchorLeft;
|
|
13077
|
+
var glideY = lastAnchor.top - anchorTop;
|
|
13078
|
+
if (glideX !== 0 || glideY !== 0) {
|
|
13079
|
+
try {
|
|
13080
|
+
shadow.style.transform = "translate(" + glideX + "px," + glideY + "px)";
|
|
13081
|
+
// One forced layout commits that frame, so the next write has a value
|
|
13082
|
+
// to leave FROM; without it both writes land in one recalc and the
|
|
13083
|
+
// glide is the same jump it was meant to remove.
|
|
13084
|
+
shadow.getBoundingClientRect();
|
|
13085
|
+
shadow.style.transition = "transform " + FLOAT_SNAP_MS + "ms ease";
|
|
13086
|
+
shadow.style.transform = "none";
|
|
13087
|
+
} catch (error) {
|
|
13088
|
+
shadow.style.transform = "";
|
|
13089
|
+
}
|
|
13090
|
+
}
|
|
13091
|
+
}
|
|
13092
|
+
lastAnchor = { left: anchorLeft, top: anchorTop };
|
|
12829
13093
|
// Enter animation, only on a real shut->open transition: the card starts
|
|
12830
13094
|
// clipped to the dot's own rectangle, a hair smaller and dimmer, and the
|
|
12831
13095
|
// next frames let the transition carry all three home (clip/transform/
|
package/lib/shared.cjs
CHANGED
|
@@ -1185,6 +1185,15 @@ function floatBoundsPick(rects, viewport) {
|
|
|
1185
1185
|
if (!isFinite(vh) || vh <= 0) vh = FLOAT_VIEWPORT_H;
|
|
1186
1186
|
var full = { left: 0, top: 0, right: vw, bottom: vh, width: vw, height: vh };
|
|
1187
1187
|
var list = Array.isArray(rects) ? rects : [];
|
|
1188
|
+
// A region narrower than one dot used to count as "no region at all", and the
|
|
1189
|
+
// fallback was the WHOLE viewport — so in a narrow window the dot parked on the
|
|
1190
|
+
// window's own corner, over the sidebar, instead of on the conversation's. The
|
|
1191
|
+
// largest readable box is kept as a fallback, and it names itself in `index` so
|
|
1192
|
+
// the watcher holds the box whose size actually defines the region. `full` is
|
|
1193
|
+
// left for a page with no region at all — or only degenerate 0-area rects.
|
|
1194
|
+
var best = null;
|
|
1195
|
+
var bestIndex = -1;
|
|
1196
|
+
var bestArea = -1;
|
|
1188
1197
|
for (var index = 0; index < list.length; index += 1) {
|
|
1189
1198
|
var box = floatBoxOf(list[index]);
|
|
1190
1199
|
if (box === null) continue;
|
|
@@ -1194,12 +1203,18 @@ function floatBoundsPick(rects, viewport) {
|
|
|
1194
1203
|
var bottom = Math.max(0, Math.min(vh, box.bottom));
|
|
1195
1204
|
var width = right - left;
|
|
1196
1205
|
var height = bottom - top;
|
|
1206
|
+
if (width > 0 && height > 0 && width * height > bestArea) {
|
|
1207
|
+
bestArea = width * height;
|
|
1208
|
+
bestIndex = index;
|
|
1209
|
+
best = { left: left, top: top, right: right, bottom: bottom, width: width, height: height };
|
|
1210
|
+
}
|
|
1197
1211
|
if (width < FLOAT_BOUNDS_MIN || height < FLOAT_BOUNDS_MIN) continue;
|
|
1198
1212
|
return {
|
|
1199
1213
|
index: index,
|
|
1200
1214
|
bounds: { left: left, top: top, right: right, bottom: bottom, width: width, height: height },
|
|
1201
1215
|
};
|
|
1202
1216
|
}
|
|
1217
|
+
if (best !== null) return { index: bestIndex, bounds: best };
|
|
1203
1218
|
return { index: -1, bounds: full };
|
|
1204
1219
|
}
|
|
1205
1220
|
|
|
@@ -1318,6 +1333,36 @@ function floatTransformOrigin(quadrant) {
|
|
|
1318
1333
|
return "100% 100%";
|
|
1319
1334
|
}
|
|
1320
1335
|
|
|
1336
|
+
/**
|
|
1337
|
+
* Which side the open CARD grows towards: the one with more room for it.
|
|
1338
|
+
*
|
|
1339
|
+
* The card hangs off the dot, so it can only extend one way from it, and the
|
|
1340
|
+
* side that holds more of the card is the side the conversation has room for.
|
|
1341
|
+
* Each axis compares the room beyond the dot's own box on either side and sends
|
|
1342
|
+
* the card to the larger one — which is also the side its middle ends up on, so
|
|
1343
|
+
* "the panel goes to the corner it sits in" holds by construction.
|
|
1344
|
+
*
|
|
1345
|
+
* This is the CARD's rule, not the dot's: the dot keeps the corner it remembers
|
|
1346
|
+
* living in (see `floatCornerPoint`), while a rule shared by both is what made
|
|
1347
|
+
* the card jump whenever the dot crossed a line the card had not. The answer
|
|
1348
|
+
* depends on the dot and the region and on nothing else — never on a card it
|
|
1349
|
+
* just placed — so it cannot vote itself back and forth.
|
|
1350
|
+
* @param {unknown} bounds - the region, as `floatBoundsFrom` returns it.
|
|
1351
|
+
* @param {number} x - the dot's left edge, in CSS pixels.
|
|
1352
|
+
* @param {number} y - the dot's top edge, in CSS pixels.
|
|
1353
|
+
* @param {number} [dot] - the dot's diameter (defaults to FLOAT_DOT).
|
|
1354
|
+
* @returns {string} one of "tl", "tr", "bl", "br" — the quadrant the card is
|
|
1355
|
+
* anchored on, which is the corner it grows away from.
|
|
1356
|
+
*/
|
|
1357
|
+
function floatCardCorner(bounds, x, y, dot) {
|
|
1358
|
+
var box = floatBoxOf(bounds);
|
|
1359
|
+
if (box === null) box = { left: 0, top: 0, right: FLOAT_VIEWPORT_W, bottom: FLOAT_VIEWPORT_H };
|
|
1360
|
+
var size = dot === undefined ? FLOAT_DOT : dot;
|
|
1361
|
+
var growRight = box.right - x >= x + size - box.left;
|
|
1362
|
+
var growDown = box.bottom - y >= y + size - box.top;
|
|
1363
|
+
return (growDown ? "t" : "b") + (growRight ? "l" : "r");
|
|
1364
|
+
}
|
|
1365
|
+
|
|
1321
1366
|
/**
|
|
1322
1367
|
* Which corner of the card lands on the dot, and where the expansion starts.
|
|
1323
1368
|
*
|
|
@@ -4614,6 +4659,7 @@ var shared = {
|
|
|
4614
4659
|
floatExpandDirection: floatExpandDirection,
|
|
4615
4660
|
floatTransformOrigin: floatTransformOrigin,
|
|
4616
4661
|
floatCardAnchor: floatCardAnchor,
|
|
4662
|
+
floatCardCorner: floatCardCorner,
|
|
4617
4663
|
floatClipStart: floatClipStart,
|
|
4618
4664
|
floatClipRest: floatClipRest,
|
|
4619
4665
|
isRestClip: isRestClip,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-fonttune",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.1",
|
|
4
4
|
"description": "Font plugin for the DeepSeek Harness Web GUI: the conversation gets its own font, size, line height and weight, the interface font and weight follow it, code keeps its own axis with ligature control, and whole setups save as presets.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"icon": "icon.svg",
|