@starlove/orbit 1.3.4 → 1.3.5
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/full/index.html +4 -4
- package/dist/full/js/{app.fb7767dc.js → app.4ae80764.js} +75 -75
- package/dist/full/js/app.4ae80764.js.map +7 -0
- package/dist/full/styles.b9e0c130.css +1 -0
- package/dist/full/version.json +2 -2
- package/package.json +1 -1
- package/public/index.html +2 -2
- package/public/js/kanban.js +123 -1
- package/public/styles.css +56 -14
- package/dist/full/js/app.fb7767dc.js.map +0 -7
- package/dist/full/styles.bbc542f5.css +0 -1
package/public/js/kanban.js
CHANGED
|
@@ -513,10 +513,13 @@ export function enableKanbanDragScroll() {
|
|
|
513
513
|
|
|
514
514
|
kanban.addEventListener("pointerdown", (event) => {
|
|
515
515
|
if (event.button !== 0) return;
|
|
516
|
+
// Clear stale drag-click suppression on any fresh primary press — even over
|
|
517
|
+
// a card — so a both-button / two-finger pan that emitted no trailing click
|
|
518
|
+
// can't swallow the next genuine click.
|
|
519
|
+
dragged = false;
|
|
516
520
|
if (shouldIgnore(event.target)) return;
|
|
517
521
|
|
|
518
522
|
active = true;
|
|
519
|
-
dragged = false;
|
|
520
523
|
pointerId = event.pointerId;
|
|
521
524
|
startX = event.clientX;
|
|
522
525
|
startScroll = kanban.scrollLeft;
|
|
@@ -569,4 +572,123 @@ export function enableKanbanDragScroll() {
|
|
|
569
572
|
},
|
|
570
573
|
true
|
|
571
574
|
);
|
|
575
|
+
|
|
576
|
+
// --- Both-button mouse drag + two-finger touch drag -----------------------
|
|
577
|
+
// A second panning affordance that works ANYWHERE on the board, including
|
|
578
|
+
// directly over cards. A plain left-click / single-finger tap is left to the
|
|
579
|
+
// cards (so they still open + drag); holding *both* mouse buttons, or dragging
|
|
580
|
+
// with *two fingers*, pans the board horizontally without engaging a card.
|
|
581
|
+
let bothBtnPanning = false;
|
|
582
|
+
let bothBtnLastX = 0;
|
|
583
|
+
let suppressContextMenu = false;
|
|
584
|
+
|
|
585
|
+
// Pressing the second mouse button (buttons bitmask 1|2 === 3) arms the pan
|
|
586
|
+
// and cancels any single-button drag already in flight (see `active` above).
|
|
587
|
+
kanban.addEventListener("pointerdown", (event) => {
|
|
588
|
+
if (event.pointerType !== "mouse" || (event.buttons & 3) !== 3) return;
|
|
589
|
+
bothBtnPanning = true;
|
|
590
|
+
bothBtnLastX = event.clientX;
|
|
591
|
+
suppressContextMenu = true;
|
|
592
|
+
active = false; // hand control off from the left-button drag
|
|
593
|
+
kanban.classList.add("is-grabbing");
|
|
594
|
+
});
|
|
595
|
+
|
|
596
|
+
kanban.addEventListener("pointermove", (event) => {
|
|
597
|
+
if (event.pointerType !== "mouse") return;
|
|
598
|
+
const bothDown = (event.buttons & 3) === 3;
|
|
599
|
+
if (bothDown) {
|
|
600
|
+
event.preventDefault();
|
|
601
|
+
if (!bothBtnPanning) {
|
|
602
|
+
bothBtnPanning = true;
|
|
603
|
+
bothBtnLastX = event.clientX;
|
|
604
|
+
suppressContextMenu = true;
|
|
605
|
+
active = false;
|
|
606
|
+
kanban.classList.add("is-grabbing");
|
|
607
|
+
return;
|
|
608
|
+
}
|
|
609
|
+
const delta = event.clientX - bothBtnLastX;
|
|
610
|
+
if (delta) {
|
|
611
|
+
kanban.scrollLeft -= delta;
|
|
612
|
+
bothBtnLastX = event.clientX;
|
|
613
|
+
dragged = true; // eat the trailing synthetic click
|
|
614
|
+
}
|
|
615
|
+
} else if (bothBtnPanning) {
|
|
616
|
+
bothBtnPanning = false;
|
|
617
|
+
kanban.classList.remove("is-grabbing");
|
|
618
|
+
}
|
|
619
|
+
});
|
|
620
|
+
|
|
621
|
+
kanban.addEventListener("pointerup", (event) => {
|
|
622
|
+
if (event.pointerType === "mouse" && bothBtnPanning && (event.buttons & 3) !== 3) {
|
|
623
|
+
bothBtnPanning = false;
|
|
624
|
+
kanban.classList.remove("is-grabbing");
|
|
625
|
+
}
|
|
626
|
+
});
|
|
627
|
+
|
|
628
|
+
// Right-click handling on the board:
|
|
629
|
+
// - after a both-button pan, swallow the trailing context menu;
|
|
630
|
+
// - otherwise suppress the native browser menu everywhere EXCEPT over a card,
|
|
631
|
+
// which owns its own context menu (see bindCardContextMenuDelegated).
|
|
632
|
+
kanban.addEventListener("contextmenu", (event) => {
|
|
633
|
+
if (suppressContextMenu) {
|
|
634
|
+
event.preventDefault();
|
|
635
|
+
event.stopPropagation();
|
|
636
|
+
suppressContextMenu = false;
|
|
637
|
+
return;
|
|
638
|
+
}
|
|
639
|
+
const onCard = event.target instanceof Element && event.target.closest(".card");
|
|
640
|
+
if (!onCard) {
|
|
641
|
+
event.preventDefault();
|
|
642
|
+
event.stopPropagation();
|
|
643
|
+
}
|
|
644
|
+
});
|
|
645
|
+
|
|
646
|
+
// Two-finger touch pan. Touch events (not pointer events) are used here so we
|
|
647
|
+
// can reliably preventDefault the browser's native two-finger scroll/zoom.
|
|
648
|
+
let touchPanning = false;
|
|
649
|
+
let touchPanLastX = 0;
|
|
650
|
+
const twoFingerCentroidX = (touches) => (touches[0].clientX + touches[1].clientX) / 2;
|
|
651
|
+
|
|
652
|
+
kanban.addEventListener(
|
|
653
|
+
"touchstart",
|
|
654
|
+
(event) => {
|
|
655
|
+
// Fresh gesture — clear any leftover drag-click suppression so a real tap
|
|
656
|
+
// is never swallowed when the prior two-finger pan emitted no click.
|
|
657
|
+
dragged = false;
|
|
658
|
+
if (event.touches.length === 2) {
|
|
659
|
+
touchPanning = true;
|
|
660
|
+
touchPanLastX = twoFingerCentroidX(event.touches);
|
|
661
|
+
active = false; // cancel any single-finger drag that started
|
|
662
|
+
kanban.classList.add("is-grabbing");
|
|
663
|
+
} else {
|
|
664
|
+
touchPanning = false;
|
|
665
|
+
}
|
|
666
|
+
},
|
|
667
|
+
{ passive: true }
|
|
668
|
+
);
|
|
669
|
+
|
|
670
|
+
kanban.addEventListener(
|
|
671
|
+
"touchmove",
|
|
672
|
+
(event) => {
|
|
673
|
+
if (!touchPanning || event.touches.length !== 2) return;
|
|
674
|
+
const x = twoFingerCentroidX(event.touches);
|
|
675
|
+
const delta = x - touchPanLastX;
|
|
676
|
+
if (delta) {
|
|
677
|
+
event.preventDefault();
|
|
678
|
+
kanban.scrollLeft -= delta;
|
|
679
|
+
touchPanLastX = x;
|
|
680
|
+
dragged = true; // eat the trailing tap so a card doesn't open
|
|
681
|
+
}
|
|
682
|
+
},
|
|
683
|
+
{ passive: false }
|
|
684
|
+
);
|
|
685
|
+
|
|
686
|
+
const endTouchPan = (event) => {
|
|
687
|
+
if (event.touches.length < 2 && touchPanning) {
|
|
688
|
+
touchPanning = false;
|
|
689
|
+
kanban.classList.remove("is-grabbing");
|
|
690
|
+
}
|
|
691
|
+
};
|
|
692
|
+
kanban.addEventListener("touchend", endTouchPan, { passive: true });
|
|
693
|
+
kanban.addEventListener("touchcancel", endTouchPan, { passive: true });
|
|
572
694
|
}
|
package/public/styles.css
CHANGED
|
@@ -2376,10 +2376,20 @@ select.detail-meta-badge::-ms-expand {
|
|
|
2376
2376
|
grid-area: tabs;
|
|
2377
2377
|
display: flex;
|
|
2378
2378
|
gap: 0.3rem;
|
|
2379
|
+
/* min-width:0 lets this grid item shrink to its column so the tabs overflow
|
|
2380
|
+
*internally* and scroll, rather than being clipped by the drawer's
|
|
2381
|
+
overflow:hidden. */
|
|
2382
|
+
min-width: 0;
|
|
2379
2383
|
overflow-x: auto;
|
|
2384
|
+
overscroll-behavior-x: contain;
|
|
2385
|
+
-webkit-overflow-scrolling: touch;
|
|
2380
2386
|
scrollbar-width: none;
|
|
2381
2387
|
}
|
|
2382
2388
|
|
|
2389
|
+
.drawer-tabs::-webkit-scrollbar {
|
|
2390
|
+
display: none;
|
|
2391
|
+
}
|
|
2392
|
+
|
|
2383
2393
|
.drawer-tabs[hidden] {
|
|
2384
2394
|
display: none;
|
|
2385
2395
|
}
|
|
@@ -4210,7 +4220,9 @@ details.section > summary .mcp-title-row {
|
|
|
4210
4220
|
|
|
4211
4221
|
.lane-row {
|
|
4212
4222
|
display: grid;
|
|
4213
|
-
|
|
4223
|
+
/* ↑ ↓ | name (flex) | New | × — every control is a 2rem square except the
|
|
4224
|
+
name field, so the cluster stays tight at every width (fits phones too). */
|
|
4225
|
+
grid-template-columns: 2rem 2rem minmax(4rem, 1fr) 2rem 2rem;
|
|
4214
4226
|
gap: 0.4rem;
|
|
4215
4227
|
align-items: center;
|
|
4216
4228
|
border: 1px solid var(--line);
|
|
@@ -4451,7 +4463,8 @@ button:disabled {
|
|
|
4451
4463
|
|
|
4452
4464
|
.lane-create-form {
|
|
4453
4465
|
display: grid;
|
|
4454
|
-
|
|
4466
|
+
/* name field (flex) + Add Lane button — two children, two columns. */
|
|
4467
|
+
grid-template-columns: minmax(6rem, 1fr) auto;
|
|
4455
4468
|
gap: 0.45rem;
|
|
4456
4469
|
margin-top: 0.65rem;
|
|
4457
4470
|
}
|
|
@@ -4873,22 +4886,47 @@ button.toast-close::after {
|
|
|
4873
4886
|
|
|
4874
4887
|
/* Delay topbar wrap until small tablet/large phone widths. */
|
|
4875
4888
|
@media (max-width: 800px) {
|
|
4889
|
+
/* Let the topbar wrap into rows instead of stacking the brand above the
|
|
4890
|
+
controls. Dissolving the `.controls` box (display:contents) promotes its
|
|
4891
|
+
children into the topbar's own wrap flow, so the brand and the notes strip
|
|
4892
|
+
share the first row (notes beside the badge, as on desktop) while search
|
|
4893
|
+
and the action buttons fall onto their own rows below. */
|
|
4876
4894
|
.topbar {
|
|
4877
4895
|
position: static;
|
|
4878
|
-
|
|
4879
|
-
|
|
4896
|
+
flex-wrap: wrap;
|
|
4897
|
+
align-items: center;
|
|
4898
|
+
justify-content: flex-start;
|
|
4899
|
+
column-gap: 0.5rem;
|
|
4900
|
+
row-gap: 0.55rem;
|
|
4880
4901
|
}
|
|
4881
4902
|
|
|
4882
4903
|
.controls {
|
|
4883
|
-
|
|
4884
|
-
|
|
4904
|
+
display: contents;
|
|
4905
|
+
}
|
|
4906
|
+
|
|
4907
|
+
.topbar-controls-gap {
|
|
4908
|
+
display: none;
|
|
4885
4909
|
}
|
|
4886
4910
|
|
|
4887
|
-
|
|
4911
|
+
/* Clickable, truncated board-notes strip sits right after the badge, hugging
|
|
4912
|
+
its text and ellipsizing if it runs long. Hidden when empty so the badge
|
|
4913
|
+
keeps the row to itself (the logo still opens the notes popover). */
|
|
4888
4914
|
.brand-notes-strip {
|
|
4915
|
+
flex: 0 1 auto;
|
|
4916
|
+
min-width: 0;
|
|
4917
|
+
--topbar-notes-max-width: 100%;
|
|
4918
|
+
}
|
|
4919
|
+
|
|
4920
|
+
.brand-notes-strip[data-empty="true"] {
|
|
4889
4921
|
display: none;
|
|
4890
4922
|
}
|
|
4891
4923
|
|
|
4924
|
+
/* Reclaim vertical space for the board on mobile. */
|
|
4925
|
+
.frame-footer {
|
|
4926
|
+
display: none;
|
|
4927
|
+
}
|
|
4928
|
+
|
|
4929
|
+
/* flex-basis 100% forces search onto its own full-width row below the brand. */
|
|
4892
4930
|
.topbar-search {
|
|
4893
4931
|
flex: 1 1 100%;
|
|
4894
4932
|
width: 100%;
|
|
@@ -4896,14 +4934,12 @@ button.toast-close::after {
|
|
|
4896
4934
|
max-width: none;
|
|
4897
4935
|
}
|
|
4898
4936
|
|
|
4899
|
-
/* On the wrapped action row,
|
|
4937
|
+
/* On the wrapped action-button row, anchor the utility controls to the right. */
|
|
4900
4938
|
#themeToggle {
|
|
4901
4939
|
margin-left: auto;
|
|
4902
4940
|
}
|
|
4903
4941
|
|
|
4904
|
-
.composer-row
|
|
4905
|
-
.lane-row,
|
|
4906
|
-
.lane-create-form {
|
|
4942
|
+
.composer-row {
|
|
4907
4943
|
grid-template-columns: 1fr 1fr;
|
|
4908
4944
|
}
|
|
4909
4945
|
|
|
@@ -4917,10 +4953,16 @@ button.toast-close::after {
|
|
|
4917
4953
|
flex-wrap: wrap;
|
|
4918
4954
|
}
|
|
4919
4955
|
|
|
4956
|
+
/* Phone: collapse the action controls to icon-only so New Card, Board, theme,
|
|
4957
|
+
Help, and Settings all fit on a single line. */
|
|
4958
|
+
.btn-sun-label,
|
|
4959
|
+
.topbar-chip-label,
|
|
4960
|
+
.topbar-btn-label {
|
|
4961
|
+
display: none;
|
|
4962
|
+
}
|
|
4963
|
+
|
|
4920
4964
|
.composer-row,
|
|
4921
|
-
.deployment-actions
|
|
4922
|
-
.lane-row,
|
|
4923
|
-
.lane-create-form {
|
|
4965
|
+
.deployment-actions {
|
|
4924
4966
|
grid-template-columns: 1fr;
|
|
4925
4967
|
}
|
|
4926
4968
|
|