@guidelytech/guide-runtime 0.11.0 → 0.13.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.
@@ -20,6 +20,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
20
20
  // src/messenger.ts
21
21
  var messenger_exports = {};
22
22
  __export(messenger_exports, {
23
+ parseLauncherSpot: () => parseLauncherSpot,
23
24
  personalGreeting: () => personalGreeting,
24
25
  relativeTime: () => relativeTime,
25
26
  renderMessenger: () => renderMessenger
@@ -1843,6 +1844,107 @@ function createIdeas(o) {
1843
1844
  };
1844
1845
  }
1845
1846
 
1847
+ // src/launcher-drag.ts
1848
+ var DRAG_THRESHOLD = 6;
1849
+ var MARGIN = 16;
1850
+ function parseLauncherSpot(raw) {
1851
+ let v = raw;
1852
+ if (typeof v === "string") {
1853
+ try {
1854
+ v = JSON.parse(v);
1855
+ } catch {
1856
+ return null;
1857
+ }
1858
+ }
1859
+ if (!v || typeof v !== "object") return null;
1860
+ const { side, y } = v;
1861
+ if (side !== "left" && side !== "right" || typeof y !== "number" || !Number.isFinite(y)) return null;
1862
+ return { side, y: Math.min(1, Math.max(0, y)) };
1863
+ }
1864
+ function maxLauncherBottom(viewportHeight, size) {
1865
+ return Math.max(0, viewportHeight - size - MARGIN * 2);
1866
+ }
1867
+ function snapLauncher(drop, viewport, size) {
1868
+ const side = drop.left + size / 2 < viewport.width / 2 ? "left" : "right";
1869
+ const max = maxLauncherBottom(viewport.height, size);
1870
+ const bottom = Math.min(max, Math.max(0, viewport.height - MARGIN - size - drop.top));
1871
+ return { side, y: max ? bottom / max : 0 };
1872
+ }
1873
+ function enableLauncherDrag({ launcher, onMove }) {
1874
+ const win = launcher.ownerDocument.defaultView ?? window;
1875
+ let start = null;
1876
+ let dragging = false;
1877
+ let swallowClickUntil = 0;
1878
+ const onDown = (e) => {
1879
+ if (e.button !== 0) return;
1880
+ const r = launcher.getBoundingClientRect();
1881
+ start = { id: e.pointerId, x: e.clientX, y: e.clientY, left: r.left, top: r.top };
1882
+ dragging = false;
1883
+ try {
1884
+ launcher.setPointerCapture(e.pointerId);
1885
+ } catch {
1886
+ }
1887
+ };
1888
+ const onMovePointer = (e) => {
1889
+ if (!start || e.pointerId !== start.id) return;
1890
+ const dx = e.clientX - start.x;
1891
+ const dy = e.clientY - start.y;
1892
+ if (!dragging) {
1893
+ if (Math.hypot(dx, dy) < DRAG_THRESHOLD) return;
1894
+ dragging = true;
1895
+ launcher.classList.add("g-m-dragging");
1896
+ }
1897
+ e.preventDefault();
1898
+ const size = launcher.offsetWidth || 56;
1899
+ const left = Math.min(win.innerWidth - size - MARGIN, Math.max(MARGIN, start.left + dx));
1900
+ const top = Math.min(win.innerHeight - size - MARGIN, Math.max(MARGIN, start.top + dy));
1901
+ Object.assign(launcher.style, { left: `${left}px`, top: `${top}px`, right: "auto", bottom: "auto", margin: "0" });
1902
+ };
1903
+ const onUp = (e) => {
1904
+ if (!start || e.pointerId !== start.id) return;
1905
+ start = null;
1906
+ if (!dragging) return;
1907
+ dragging = false;
1908
+ swallowClickUntil = Date.now() + 400;
1909
+ const before = launcher.getBoundingClientRect();
1910
+ launcher.classList.remove("g-m-dragging");
1911
+ Object.assign(launcher.style, { left: "", top: "", right: "", margin: "" });
1912
+ onMove(snapLauncher({ left: before.left, top: before.top }, { width: win.innerWidth, height: win.innerHeight }, before.width || 56));
1913
+ const after = launcher.getBoundingClientRect();
1914
+ const dx = before.left - after.left;
1915
+ const dy = before.top - after.top;
1916
+ if (!dx && !dy) return;
1917
+ launcher.style.transition = "none";
1918
+ launcher.style.transform = `translate(${dx}px, ${dy}px)`;
1919
+ void launcher.offsetWidth;
1920
+ launcher.style.transition = "";
1921
+ launcher.classList.add("g-m-snapping");
1922
+ launcher.style.transform = "";
1923
+ const done = () => launcher.classList.remove("g-m-snapping");
1924
+ launcher.addEventListener("transitionend", done, { once: true });
1925
+ win.setTimeout(done, 400);
1926
+ };
1927
+ const onClick = (e) => {
1928
+ if (Date.now() < swallowClickUntil) {
1929
+ swallowClickUntil = 0;
1930
+ e.preventDefault();
1931
+ e.stopImmediatePropagation();
1932
+ }
1933
+ };
1934
+ launcher.addEventListener("pointerdown", onDown);
1935
+ launcher.addEventListener("pointermove", onMovePointer);
1936
+ launcher.addEventListener("pointerup", onUp);
1937
+ launcher.addEventListener("pointercancel", onUp);
1938
+ launcher.addEventListener("click", onClick, true);
1939
+ return () => {
1940
+ launcher.removeEventListener("pointerdown", onDown);
1941
+ launcher.removeEventListener("pointermove", onMovePointer);
1942
+ launcher.removeEventListener("pointerup", onUp);
1943
+ launcher.removeEventListener("pointercancel", onUp);
1944
+ launcher.removeEventListener("click", onClick, true);
1945
+ };
1946
+ }
1947
+
1846
1948
  // src/messenger-styles.ts
1847
1949
  var MESSENGER_STYLES = `
1848
1950
  :host([data-g-messenger="bottom-right"]) .g-launcher.g-pos-bottom-right{right:72px}
@@ -1851,9 +1953,13 @@ var MESSENGER_STYLES = `
1851
1953
  .g-m-launcher:hover{transform:scale(1.05)}
1852
1954
  .g-m-launcher:focus-visible{outline:2px solid var(--g-primary);outline-offset:3px}
1853
1955
  .g-m-launcher svg{width:26px;height:26px;fill:currentColor}
1956
+ .g-m-launcher{touch-action:none;-webkit-user-select:none;user-select:none}
1957
+ .g-m-launcher.g-m-dragging{transition:none;cursor:grabbing;transform:scale(1.08)}
1958
+ .g-m-launcher.g-m-snapping{transition:transform .3s cubic-bezier(.2,.8,.2,1)}
1854
1959
  .g-m-badge{position:absolute;top:-2px;right:-2px;min-width:20px;height:20px;padding:0 5px;border-radius:999px;background:#e11d48;color:#fff;font-size:11px;font-weight:700;display:inline-flex;align-items:center;justify-content:center;border:2px solid var(--g-bg)}
1855
- .g-messenger{bottom:76px;height:min(640px,calc(100vh - 110px));transition:width .2s ease,height .2s ease,max-height .2s ease}
1856
- .g-messenger.g-m-expanded{width:min(720px,calc(100vw - 32px));height:calc(100vh - 110px);max-height:calc(100vh - 110px)}
1960
+ .g-messenger{bottom:var(--g-m-bottom,76px);height:min(640px,calc(100vh - var(--g-m-reserve,110px)));transition:width .2s ease,height .2s ease,max-height .2s ease}
1961
+ .g-messenger.g-m-expanded{width:min(720px,calc(100vw - 32px));height:calc(100vh - var(--g-m-reserve,110px));max-height:calc(100vh - var(--g-m-reserve,110px))}
1962
+ .g-messenger.g-m-below{bottom:auto;top:var(--g-m-top)}
1857
1963
  .g-m-expanded .g-rc-article,.g-m-expanded .g-m-scroll{padding:24px clamp(20px,5vw,40px)}
1858
1964
  .g-m-expanded .g-rc-article>.g-title{font-size:24px;line-height:1.25;margin-bottom:12px}
1859
1965
  .g-m-expanded .g-rc-article .g-body{font-size:15px}
@@ -1905,7 +2011,7 @@ var MESSENGER_STYLES = `
1905
2011
  .g-m-typing span{width:6px;height:6px;border-radius:999px;background:var(--g-muted);animation:g-m-bounce 1s infinite}
1906
2012
  .g-m-typing span:nth-child(2){animation-delay:.15s}.g-m-typing span:nth-child(3){animation-delay:.3s}
1907
2013
  @keyframes g-m-bounce{0%,60%,100%{opacity:.3;transform:translateY(0)}30%{opacity:1;transform:translateY(-3px)}}
1908
- @media (prefers-reduced-motion: reduce){.g-m-typing span{animation:none}.g-m-launcher{transition:none}}
2014
+ @media (prefers-reduced-motion: reduce){.g-m-typing span{animation:none}.g-m-launcher,.g-m-launcher.g-m-snapping{transition:none}}
1909
2015
  .g-m-chips{display:flex;flex-direction:column;gap:6px;margin-top:2px}
1910
2016
  .g-m-chip{display:flex;align-items:center;gap:8px;text-align:left;border:1px solid var(--g-border);background:var(--g-bg);color:var(--g-text);border-radius:10px;padding:8px 10px;font:inherit;font-size:13px;cursor:pointer}
1911
2017
  .g-m-chip:hover{background:var(--g-subtle)}
@@ -2254,7 +2360,6 @@ function renderMessenger(options) {
2254
2360
  const pos = `g-pos-${config.launcher.position}`;
2255
2361
  const rootNode = scope.getRootNode();
2256
2362
  const shadowHost = "host" in rootNode ? rootNode.host : null;
2257
- shadowHost?.setAttribute("data-g-messenger", config.launcher.position);
2258
2363
  const root = el(doc, "div", "g-messenger-root");
2259
2364
  const launcher = el(doc, "button", `g-m-launcher ${pos}`);
2260
2365
  launcher.type = "button";
@@ -2266,6 +2371,47 @@ function renderMessenger(options) {
2266
2371
  launcher.appendChild(badge);
2267
2372
  if (options.hideLauncher) launcher.hidden = true;
2268
2373
  const panel = el(doc, "div", `g-panel g-messenger ${pos}`);
2374
+ let spot = options.launcherSpot ?? null;
2375
+ const win = doc.defaultView ?? window;
2376
+ function placeLauncher() {
2377
+ const side = spot?.side ?? (config.launcher.position === "bottom-left" ? "left" : "right");
2378
+ for (const node of [launcher, panel]) {
2379
+ node.classList.toggle("g-pos-bottom-left", side === "left");
2380
+ node.classList.toggle("g-pos-bottom-right", side === "right");
2381
+ }
2382
+ const size = launcher.offsetWidth || 56;
2383
+ const bottom = spot && !options.hideLauncher ? Math.round(spot.y * maxLauncherBottom(win.innerHeight, size)) : 0;
2384
+ launcher.style.bottom = bottom ? `${bottom}px` : "";
2385
+ if (bottom < size + 16) shadowHost?.setAttribute("data-g-messenger", `bottom-${side}`);
2386
+ else shadowHost?.removeAttribute("data-g-messenger");
2387
+ const launcherTop = win.innerHeight - 16 - size - bottom;
2388
+ const below = bottom > 0 && launcherTop < 480 && bottom > launcherTop;
2389
+ panel.classList.toggle("g-m-below", below);
2390
+ if (!bottom) {
2391
+ panel.style.removeProperty("--g-m-bottom");
2392
+ panel.style.removeProperty("--g-m-top");
2393
+ panel.style.removeProperty("--g-m-reserve");
2394
+ } else if (below) {
2395
+ const top = launcherTop + size - 12;
2396
+ panel.style.setProperty("--g-m-top", `${top}px`);
2397
+ panel.style.setProperty("--g-m-reserve", `${top + 40}px`);
2398
+ } else {
2399
+ panel.style.setProperty("--g-m-bottom", `${bottom + 76}px`);
2400
+ panel.style.setProperty("--g-m-reserve", `${bottom + 110}px`);
2401
+ }
2402
+ }
2403
+ const stopDrag = enableLauncherDrag({
2404
+ launcher,
2405
+ onMove(next) {
2406
+ spot = next;
2407
+ placeLauncher();
2408
+ options.onLauncherMove?.(next);
2409
+ }
2410
+ });
2411
+ const onResize = () => {
2412
+ if (spot) placeLauncher();
2413
+ };
2414
+ win.addEventListener("resize", onResize);
2269
2415
  panel.setAttribute("role", "dialog");
2270
2416
  panel.setAttribute("aria-label", config.name);
2271
2417
  panel.hidden = true;
@@ -2276,6 +2422,7 @@ function renderMessenger(options) {
2276
2422
  panel.append(head, main, tabs);
2277
2423
  root.append(launcher, panel);
2278
2424
  scope.appendChild(root);
2425
+ placeLauncher();
2279
2426
  function withRcText(items, rc, apply) {
2280
2427
  if (!rc?.length) return items;
2281
2428
  const byId = new Map(rc.map((x) => [x.id, x]));
@@ -3362,6 +3509,8 @@ function renderMessenger(options) {
3362
3509
  if (threadTimer) clearTimeout(threadTimer);
3363
3510
  if (backgroundTimer) clearInterval(backgroundTimer);
3364
3511
  panel.removeEventListener("keydown", onKey);
3512
+ stopDrag();
3513
+ win.removeEventListener("resize", onResize);
3365
3514
  help.destroy();
3366
3515
  ideas.destroy();
3367
3516
  shadowHost?.removeAttribute("data-g-messenger");
@@ -3371,6 +3520,7 @@ function renderMessenger(options) {
3371
3520
  }
3372
3521
  // Annotate the CommonJS export names for ESM import in node:
3373
3522
  0 && (module.exports = {
3523
+ parseLauncherSpot,
3374
3524
  personalGreeting,
3375
3525
  relativeTime,
3376
3526
  renderMessenger