Merge branch 'dev'
This commit is contained in:
commit
96ae343748
|
|
@ -235,6 +235,13 @@ export class FolkShape extends FolkElement {
|
|||
#lastTouchPos: Point | null = null;
|
||||
#isTouchDragging = false;
|
||||
#dragOffset: Point | null = null;
|
||||
#touchStartPos: Point | null = null;
|
||||
#longPressTimer: ReturnType<typeof setTimeout> | null = null;
|
||||
#extraLongPressTimer: ReturnType<typeof setTimeout> | null = null;
|
||||
#touchDidLongPress = false;
|
||||
static LONG_PRESS_MS = 500;
|
||||
static EXTRA_LONG_PRESS_MS = 1000;
|
||||
static TOUCH_MOVE_THRESHOLD = 8; // px before considered a drag
|
||||
|
||||
get x() {
|
||||
return this.#rect.x;
|
||||
|
|
@ -485,6 +492,11 @@ export class FolkShape extends FolkElement {
|
|||
};
|
||||
}
|
||||
|
||||
#clearLongPressTimers() {
|
||||
if (this.#longPressTimer) { clearTimeout(this.#longPressTimer); this.#longPressTimer = null; }
|
||||
if (this.#extraLongPressTimer) { clearTimeout(this.#extraLongPressTimer); this.#extraLongPressTimer = null; }
|
||||
}
|
||||
|
||||
handleEvent(event: PointerEvent | KeyboardEvent | TouchEvent) {
|
||||
// In feed mode, suppress all drag/resize interactions
|
||||
if (this.closest('#canvas.feed-mode')) return;
|
||||
|
|
@ -506,11 +518,11 @@ export class FolkShape extends FolkElement {
|
|||
|
||||
// Two-finger gesture → cancel shape drag so canvas pan takes over
|
||||
if (event.touches.length >= 2) {
|
||||
if (this.#isTouchDragging) {
|
||||
this.#lastTouchPos = null;
|
||||
this.#isTouchDragging = false;
|
||||
this.#internals.states.delete("move");
|
||||
}
|
||||
this.#clearLongPressTimers();
|
||||
this.#lastTouchPos = null;
|
||||
this.#isTouchDragging = false;
|
||||
this.#touchDidLongPress = false;
|
||||
this.#internals.states.delete("move");
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -526,16 +538,59 @@ export class FolkShape extends FolkElement {
|
|||
return;
|
||||
}
|
||||
|
||||
this.#touchStartPos = { x: touch.clientX, y: touch.clientY };
|
||||
this.#lastTouchPos = { x: touch.clientX, y: touch.clientY };
|
||||
this.#isTouchDragging = true;
|
||||
this.#internals.states.add("move");
|
||||
this.#isTouchDragging = false;
|
||||
this.#touchDidLongPress = false;
|
||||
|
||||
// Long press → select shape
|
||||
this.#longPressTimer = setTimeout(() => {
|
||||
this.#longPressTimer = null;
|
||||
this.#touchDidLongPress = true;
|
||||
// Stop any drag — switch to selected state
|
||||
this.#isTouchDragging = false;
|
||||
this.#internals.states.delete("move");
|
||||
navigator?.vibrate?.(30);
|
||||
this.dispatchEvent(new CustomEvent("touch-select", {
|
||||
bubbles: true, composed: true,
|
||||
detail: { shapeId: this.id },
|
||||
}));
|
||||
}, FolkShape.LONG_PRESS_MS);
|
||||
|
||||
// Extra long press → context menu
|
||||
this.#extraLongPressTimer = setTimeout(() => {
|
||||
this.#extraLongPressTimer = null;
|
||||
navigator?.vibrate?.(50);
|
||||
const cmEvent = new MouseEvent("contextmenu", {
|
||||
bubbles: true, cancelable: true,
|
||||
clientX: touch.clientX, clientY: touch.clientY,
|
||||
});
|
||||
this.dispatchEvent(cmEvent);
|
||||
}, FolkShape.EXTRA_LONG_PRESS_MS);
|
||||
|
||||
this.focus();
|
||||
return;
|
||||
}
|
||||
|
||||
if (event.type === "touchmove" && this.#isTouchDragging && event.touches.length === 1) {
|
||||
if (event.type === "touchmove" && event.touches.length === 1) {
|
||||
const touch = event.touches[0];
|
||||
if (this.#lastTouchPos) {
|
||||
|
||||
// Check if finger moved past threshold to start drag
|
||||
if (!this.#isTouchDragging && this.#touchStartPos) {
|
||||
const dx = touch.clientX - this.#touchStartPos.x;
|
||||
const dy = touch.clientY - this.#touchStartPos.y;
|
||||
if (Math.abs(dx) > FolkShape.TOUCH_MOVE_THRESHOLD ||
|
||||
Math.abs(dy) > FolkShape.TOUCH_MOVE_THRESHOLD) {
|
||||
// Movement → cancel long press timers, begin drag
|
||||
this.#clearLongPressTimers();
|
||||
if (!this.#touchDidLongPress) {
|
||||
this.#isTouchDragging = true;
|
||||
this.#internals.states.add("move");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (this.#isTouchDragging && this.#lastTouchPos) {
|
||||
const zoom = (window.visualViewport?.scale ?? 1) * this.#getParentScale();
|
||||
const moveDelta = {
|
||||
x: (touch.clientX - this.#lastTouchPos.x) / zoom,
|
||||
|
|
@ -548,13 +603,18 @@ export class FolkShape extends FolkElement {
|
|||
this.#rect.y += moveDelta.y;
|
||||
this.requestUpdate();
|
||||
this.#dispatchTransformEvent();
|
||||
} else {
|
||||
this.#lastTouchPos = { x: touch.clientX, y: touch.clientY };
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (event.type === "touchend") {
|
||||
this.#clearLongPressTimers();
|
||||
this.#lastTouchPos = null;
|
||||
this.#touchStartPos = null;
|
||||
this.#isTouchDragging = false;
|
||||
this.#touchDidLongPress = false;
|
||||
this.#internals.states.delete("move");
|
||||
return;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -465,6 +465,7 @@ class FolkFileBrowser extends HTMLElement {
|
|||
.upload-row { flex-direction: column; align-items: stretch; }
|
||||
.file-card { padding: 10px; }
|
||||
.card-form-row { flex-direction: column; }
|
||||
input[type="text"], select, textarea { font-size: 16px; }
|
||||
}
|
||||
</style>
|
||||
|
||||
|
|
|
|||
|
|
@ -253,6 +253,14 @@ class NotesCommentPanel extends HTMLElement {
|
|||
.new-comment-actions { display: flex; justify-content: flex-end; gap: 6px; margin-top: 6px; }
|
||||
.first-message-text { word-break: break-word; overflow-wrap: anywhere; }
|
||||
.message-text { word-break: break-word; overflow-wrap: anywhere; }
|
||||
@media (max-width: 480px) {
|
||||
.panel { max-height: none; height: 100%; }
|
||||
.thread-action { padding: 8px 10px; font-size: 12px; }
|
||||
.reply-btn, .reply-cancel-btn { padding: 8px 16px; }
|
||||
.reply-input { padding: 8px 10px; font-size: 14px; }
|
||||
.emoji-pick { padding: 6px 8px; font-size: 18px; }
|
||||
.new-comment-input { min-height: 44px; max-height: 100px; font-size: 14px; }
|
||||
}
|
||||
</style>
|
||||
<div class="panel" id="comment-panel">
|
||||
<div class="panel-title" data-action="toggle-collapse">
|
||||
|
|
|
|||
|
|
@ -2130,8 +2130,10 @@ Gear: EUR 400 (10%)</code></pre><p><em>Maya is tracking expenses in rF
|
|||
popover.className = 'url-popover';
|
||||
|
||||
const hostRect = (this.shadow.host as HTMLElement).getBoundingClientRect();
|
||||
popover.style.left = `${anchorRect.left - hostRect.left}px`;
|
||||
popover.style.top = `${anchorRect.bottom - hostRect.top + 4}px`;
|
||||
if (window.innerWidth > 640) {
|
||||
popover.style.left = `${anchorRect.left - hostRect.left}px`;
|
||||
popover.style.top = `${anchorRect.bottom - hostRect.top + 4}px`;
|
||||
}
|
||||
|
||||
const input = document.createElement('input');
|
||||
input.type = 'url';
|
||||
|
|
@ -3118,6 +3120,10 @@ Gear: EUR 400 (10%)</code></pre><p><em>Maya is tracking expenses in rF
|
|||
}
|
||||
.sbt-notebook-header:hover .sbt-nb-add { opacity: 1; }
|
||||
.sbt-nb-add:hover { color: var(--rs-primary); background: var(--rs-bg-surface-raised); }
|
||||
@media (pointer: coarse) {
|
||||
.sbt-nb-add { opacity: 0.6; }
|
||||
.sbt-nb-add:active { opacity: 1; color: var(--rs-primary); }
|
||||
}
|
||||
.sbt-notes { padding-left: 20px; }
|
||||
.sbt-note {
|
||||
display: flex; align-items: center; gap: 8px;
|
||||
|
|
@ -3201,11 +3207,16 @@ Gear: EUR 400 (10%)</code></pre><p><em>Maya is tracking expenses in rF
|
|||
@media (max-width: 480px) {
|
||||
.editor-with-comments { flex-direction: column; }
|
||||
.comment-sidebar.has-comments {
|
||||
width: 100%;
|
||||
border-left: none;
|
||||
border-top: 1px solid var(--rs-border, #e5e7eb);
|
||||
max-height: 250px;
|
||||
overflow-y: auto;
|
||||
width: 100%; border-left: none;
|
||||
border-top: 2px solid var(--rs-border, #e5e7eb);
|
||||
max-height: 250px; max-height: 40dvh;
|
||||
min-height: 120px; overflow-y: auto;
|
||||
border-radius: 12px 12px 0 0; padding-top: 4px;
|
||||
}
|
||||
.comment-sidebar.has-comments::before {
|
||||
content: ''; display: block; width: 32px; height: 4px;
|
||||
background: var(--rs-border-strong, #d1d5db); border-radius: 2px;
|
||||
margin: 0 auto 4px;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -3521,13 +3532,17 @@ Gear: EUR 400 (10%)</code></pre><p><em>Maya is tracking expenses in rF
|
|||
.mobile-sidebar-toggle { display: flex !important; }
|
||||
.editor-wrapper .editable-title { padding: 12px 14px 0; }
|
||||
.tiptap-container .tiptap { padding: 14px 16px; }
|
||||
.sidebar-footer-btn { min-height: 36px; padding: 7px 12px; }
|
||||
}
|
||||
@media (max-width: 480px) {
|
||||
.rapp-nav__btn { padding: 5px 10px; font-size: 12px; }
|
||||
.editable-title { font-size: 18px; }
|
||||
.tiptap-container .tiptap { font-size: 14px; padding: 12px 14px; min-height: 200px; }
|
||||
.editor-toolbar { padding: 3px 4px; gap: 1px; }
|
||||
.toolbar-btn { width: 26px; height: 24px; }
|
||||
.editor-toolbar { padding: 3px 4px; gap: 1px; overflow-x: auto; -webkit-overflow-scrolling: touch; flex-wrap: nowrap; }
|
||||
.toolbar-btn { width: 36px; height: 36px; }
|
||||
.toolbar-sep { display: none; }
|
||||
.code-textarea { min-height: 200px; }
|
||||
.image-preview { max-height: 240px; }
|
||||
.note-actions-bar { flex-wrap: wrap; gap: 6px; }
|
||||
.note-action-btn { padding: 5px 10px; font-size: 11px; }
|
||||
}
|
||||
|
|
@ -3570,6 +3585,13 @@ Gear: EUR 400 (10%)</code></pre><p><em>Maya is tracking expenses in rF
|
|||
border: 1px solid var(--rs-border);
|
||||
}
|
||||
.url-popover__btn--cancel:hover { color: var(--rs-text-primary); border-color: var(--rs-border-strong); }
|
||||
@media (max-width: 640px) {
|
||||
.url-popover {
|
||||
position: fixed; left: 8px !important; right: 8px !important;
|
||||
top: auto !important; bottom: max(env(safe-area-inset-bottom), 8px);
|
||||
min-width: 0; width: auto; border-radius: 12px 12px 0 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* ── Slash Menu ── */
|
||||
.slash-menu {
|
||||
|
|
@ -3604,6 +3626,12 @@ Gear: EUR 400 (10%)</code></pre><p><em>Maya is tracking expenses in rF
|
|||
font-size: 10px; color: var(--rs-text-muted); padding: 1px 6px;
|
||||
background: var(--rs-bg-surface-raised); border-radius: 3px; margin-left: auto;
|
||||
}
|
||||
@media (max-width: 480px) {
|
||||
.slash-menu { min-width: 200px; max-height: 260px; }
|
||||
.slash-menu-item { padding: 10px 12px; }
|
||||
.slash-menu-desc { display: none; }
|
||||
.slash-menu-hint { display: none; }
|
||||
}
|
||||
|
||||
/* ── Code highlighting (lowlight) ── */
|
||||
.tiptap-container .tiptap .hljs-keyword { color: #c792ea; }
|
||||
|
|
|
|||
|
|
@ -191,7 +191,11 @@ export function createSlashCommandPlugin(editor: Editor, shadowRoot: ShadowRoot)
|
|||
const shadowHost = shadowRoot.host as HTMLElement;
|
||||
const hostRect = shadowHost.getBoundingClientRect();
|
||||
|
||||
menuEl.style.left = `${coords.left - hostRect.left}px`;
|
||||
let left = coords.left - hostRect.left;
|
||||
const menuWidth = 240;
|
||||
const maxLeft = window.innerWidth - menuWidth - 8 - hostRect.left;
|
||||
left = Math.max(4, Math.min(left, maxLeft));
|
||||
menuEl.style.left = `${left}px`;
|
||||
menuEl.style.top = `${coords.bottom - hostRect.top + 4}px`;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -656,6 +656,8 @@ class FolkTasksBoard extends HTMLElement {
|
|||
.card { padding: 10px; }
|
||||
.card-title { font-size: 13px; }
|
||||
.card-meta { font-size: 10px; }
|
||||
.create-form input, .create-form select, .create-form textarea { font-size: 16px; }
|
||||
.create-form-actions button { min-height: 36px; padding: 6px 14px; }
|
||||
}
|
||||
</style>
|
||||
|
||||
|
|
|
|||
|
|
@ -3866,9 +3866,10 @@ Use real coordinates, YYYY-MM-DD dates, ISO currency codes. Ask clarifying quest
|
|||
});
|
||||
|
||||
// Track selection for MI bridge — supports Shift/Ctrl+click multi-select
|
||||
// On touch devices, first touch moves; selection comes from long-press (touch-select event)
|
||||
shape.addEventListener("pointerdown", (e) => {
|
||||
if (e.pointerType === "touch") return; // handled by touch-select
|
||||
if (e.shiftKey || e.metaKey || e.ctrlKey) {
|
||||
// Additive toggle
|
||||
if (selectedShapeIds.has(shape.id)) {
|
||||
selectedShapeIds.delete(shape.id);
|
||||
} else {
|
||||
|
|
@ -3882,6 +3883,16 @@ Use real coordinates, YYYY-MM-DD dates, ISO currency codes. Ask clarifying quest
|
|||
updateSelectionVisuals();
|
||||
});
|
||||
|
||||
// Mobile long-press → select shape
|
||||
shape.addEventListener("touch-select", (e) => {
|
||||
if (!selectedShapeIds.has(shape.id)) {
|
||||
selectedShapeIds.clear();
|
||||
selectedShapeIds.add(shape.id);
|
||||
}
|
||||
selectedShapeId = shape.id;
|
||||
updateSelectionVisuals();
|
||||
});
|
||||
|
||||
// Close button — forget (fade) instead of remove
|
||||
shape.addEventListener("close", () => {
|
||||
const did = getLocalDID();
|
||||
|
|
|
|||
|
|
@ -443,6 +443,7 @@ body.rstack-sidebar-open #toolbar {
|
|||
body.rstack-sidebar-open rstack-user-dashboard { left: 0; }
|
||||
body.rstack-sidebar-open .rspace-iframe-wrap { left: 0; }
|
||||
body.rstack-sidebar-open #toolbar { left: 12px; }
|
||||
.rapp-nav__btn, .rapp-nav__back { min-height: 36px; }
|
||||
}
|
||||
|
||||
@media (max-width: 480px) {
|
||||
|
|
@ -482,3 +483,9 @@ body.rstack-sidebar-open #toolbar {
|
|||
border-radius: 50%;
|
||||
animation: rspace-spin 0.8s linear infinite;
|
||||
}
|
||||
|
||||
/* ── Touch-device utilities ── */
|
||||
@media (pointer: coarse) {
|
||||
.hover-reveal { opacity: 0.5 !important; }
|
||||
.hover-reveal:active { opacity: 1 !important; }
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue