dsh-mobile 0.3.10 → 0.3.11

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.
@@ -192,6 +192,14 @@ html,body,#root{width:100%;height:100%;overflow:hidden}
192
192
  (0, react.useEffect)(() => {
193
193
  if (!hasSession) props.controller.closeDetails();
194
194
  }, [hasSession, props.controller]);
195
+ const sessionTitle = props.useSessions((session) => session.current === void 0 ? void 0 : session.byId[session.current]?.title);
196
+ (0, react.useEffect)(() => {
197
+ const productTitle = document.title;
198
+ if (sessionTitle !== void 0) document.title = `${sessionTitle} — ${productTitle}`;
199
+ return () => {
200
+ document.title = productTitle;
201
+ };
202
+ }, [sessionTitle]);
195
203
  (0, react.useEffect)(() => {
196
204
  const suppressAutofocus = (event) => {
197
205
  if (performance.now() >= suppressKeyboardUntil.current) return;
@@ -1 +1 @@
1
- {"version":3,"file":"mobile-layout.js","names":["useSyncExternalStore","useRef","useState","createElement"],"sources":["../src/mobile-layout-messages.ts","../src/mobile-layout.ts"],"sourcesContent":["export type MobileLayoutLanguage = 'it' | 'en' | 'zh'\n\nexport const MOBILE_LAYOUT_MESSAGES = Object.freeze({\n it: Object.freeze({ closePanels: 'Chiudi pannelli', workspaceNavigation: 'Navigazione area di lavoro e sessioni' }),\n en: Object.freeze({ closePanels: 'Close panels', workspaceNavigation: 'Workspace and session navigation' }),\n zh: Object.freeze({ closePanels: '关闭浮层', workspaceNavigation: '工作区与会话导航' }),\n})\n","import { createElement, useEffect, useRef, useState, useSyncExternalStore } from 'react'\nimport type { ReactNode } from 'react'\nimport { MOBILE_LAYOUT_MESSAGES, type MobileLayoutLanguage } from './mobile-layout-messages.js'\n\nexport { MOBILE_LAYOUT_MESSAGES } from './mobile-layout-messages.js'\nexport type { MobileLayoutLanguage } from './mobile-layout-messages.js'\n\ninterface SessionState {\n readonly current?: string\n readonly byId: Readonly<Record<string, { readonly blank?: boolean } | undefined>>\n}\n\ninterface MobileRootProps {\n readonly renderSlot: (name: string, owner: Record<string, unknown>) => ReactNode\n readonly useSessions: <T>(selector: (state: SessionState) => T) => T\n}\n\ninterface MobileClientContext {\n readonly effect: (effect: () => void | (() => void), label?: string) => void\n readonly on: (event: string, listener: (value: ThemeSnapshot) => void) => () => void\n readonly reflect: { provide: (name: string, value: unknown) => () => void | Promise<void> }\n readonly slots: {\n register: (options: Record<string, unknown>, component: (props: MobileRootProps) => ReactNode) => () => void\n }\n readonly theme: { getTheme: () => ThemeSnapshot }\n}\n\ninterface ThemeSnapshot {\n readonly active: {\n readonly colorScheme: 'dark' | 'light'\n readonly tokens: Readonly<Record<string, string>>\n }\n}\n\ninterface LayoutSnapshot {\n readonly sidebarOpen: boolean\n readonly detailsOpen: boolean\n}\n\n/**\n * Viewport width at which the dedicated layout treats the sidebar as a\n * persistent desktop panel instead of an overlay drawer. Narrow screens\n * keep the overlay behavior byte-for-byte.\n */\nexport const WIDE_LAYOUT_MIN_WIDTH_PX = 900\n\nexport function isWideViewportLayout(viewportWidth: number): boolean {\n return viewportWidth >= WIDE_LAYOUT_MIN_WIDTH_PX\n}\n\n/** Reads the live viewport; unknown environments (SSR, tests) stay narrow. */\nfunction viewportIsWide(): boolean {\n if (typeof window === 'undefined' || typeof window.matchMedia !== 'function') return false\n return window.matchMedia(`(min-width: ${WIDE_LAYOUT_MIN_WIDTH_PX}px)`).matches\n}\n\n/** Resolve the supported language used by the dedicated mobile layout. */\nexport function resolveMobileLayoutLanguage(\n documentLanguage: string,\n browserLanguages: readonly string[],\n): MobileLayoutLanguage {\n return [documentLanguage, ...browserLanguages]\n .map(value => value.trim().toLowerCase().split(/[-_]/u)[0])\n .find((value): value is MobileLayoutLanguage => value === 'it' || value === 'en' || value === 'zh') ?? 'en'\n}\n\nclass MobileLayoutController {\n // Wide viewports start with the persistent sidebar open; applying the\n // layout again (reconnect, refocus) reuses the module singleton below,\n // so an explicit user collapse is never reset.\n private snapshot: LayoutSnapshot = Object.freeze({ sidebarOpen: viewportIsWide(), detailsOpen: false })\n private readonly listeners = new Set<() => void>()\n\n readonly subscribe = (listener: () => void): (() => void) => {\n this.listeners.add(listener)\n return () => { this.listeners.delete(listener) }\n }\n\n readonly getSnapshot = (): LayoutSnapshot => this.snapshot\n\n toggleSidebar(): void {\n this.update({ sidebarOpen: !this.snapshot.sidebarOpen })\n }\n\n openDetails(): void {\n this.update({ detailsOpen: true })\n }\n\n closeDetails(): void {\n this.update({ detailsOpen: false })\n }\n\n closeSidebar(): void {\n this.update({ sidebarOpen: false })\n }\n\n private update(next: Partial<LayoutSnapshot>): void {\n const snapshot = Object.freeze({ ...this.snapshot, ...next })\n if (snapshot.sidebarOpen === this.snapshot.sidebarOpen && snapshot.detailsOpen === this.snapshot.detailsOpen) return\n this.snapshot = snapshot\n for (const listener of this.listeners) listener()\n }\n}\n\nclass ThemePresenter {\n private appliedTokens: string[] = []\n private readonly meta = document.createElement('meta')\n\n constructor() {\n this.meta.name = 'theme-color'\n }\n\n apply(snapshot: ThemeSnapshot): void {\n const scheme = snapshot.active.colorScheme\n document.documentElement.style.colorScheme = scheme\n document.body.toggleAttribute('data-ds-dark-theme', scheme === 'dark')\n for (const name of this.appliedTokens) document.body.style.removeProperty(name)\n this.appliedTokens = []\n for (const [name, value] of Object.entries(snapshot.active.tokens)) {\n document.body.style.setProperty(name, value)\n this.appliedTokens.push(name)\n }\n this.meta.content = getComputedStyle(document.body).backgroundColor\n if (!this.meta.isConnected) document.head.append(this.meta)\n }\n\n dispose(): void {\n document.documentElement.style.removeProperty('color-scheme')\n document.body.removeAttribute('data-ds-dark-theme')\n for (const name of this.appliedTokens) document.body.style.removeProperty(name)\n this.meta.remove()\n }\n}\n\nexport const MOBILE_LAYOUT_STYLES = `\nhtml,body,#root{width:100%;height:100%;overflow:hidden}\n.dshm-shell{position:relative;display:grid;width:100%;height:100dvh;min-width:0;overflow:hidden;background:var(--dsw-alias-bg-base,#fff)}\n.dshm-main{grid-area:1/1;min-width:0;min-height:0;overflow:hidden}\n.dshm-main>*,.dshm-main>*>*{min-width:0}\n.dshm-drawer{position:fixed;z-index:70;inset:0 auto 0 0;box-sizing:border-box;width:56px;max-width:100%;padding-top:env(safe-area-inset-top);overflow:hidden;background:var(--dsw-alias-bg-layer-1,#f8fafc);box-shadow:none;will-change:width;transition:width 240ms cubic-bezier(.22,1,.36,1),box-shadow 240ms ease}\n.dshm-drawer[data-open=true]{width:min(88vw,340px);box-shadow:18px 0 46px rgb(15 23 42 / 18%)}\n.dshm-drawer[data-open=false]{pointer-events:auto;visibility:visible}\n.dshm-drawer>*{width:100%!important;height:100%!important;transform:translateX(-6px);opacity:.94;transition:transform 220ms cubic-bezier(.22,1,.36,1),opacity 160ms ease-out}\n.dshm-drawer[data-open=true]>*{transform:translateX(0);opacity:1}\n.dshm-drawer[data-open=false]>*{width:56px!important}\n.dshm-details{position:fixed;z-index:80;inset:0 0 0 auto;box-sizing:border-box;width:min(94vw,460px);max-width:100%;padding-top:env(safe-area-inset-top);overflow:hidden;background:var(--dsw-alias-bg-layer-1,#fff);box-shadow:-18px 0 46px rgb(15 23 42 / 18%);transform:translateX(104%);transition:transform 190ms cubic-bezier(.22,1,.36,1)}\n.dshm-details[data-open=true]{transform:translateX(0)}\n.dshm-details[data-open=false]{pointer-events:none;visibility:hidden;transition:transform 190ms cubic-bezier(.22,1,.36,1),visibility 0s linear 190ms}\n.dshm-scrim{position:fixed;z-index:65;inset:0;border:0;background:rgb(15 23 42 / 40%);opacity:0;pointer-events:none;transition:opacity 180ms ease-out}\n.dshm-scrim[data-open=true]{opacity:1;pointer-events:auto}\n.dshm-overlay{position:fixed;z-index:90;inset:0;pointer-events:none}.dshm-overlay>*{pointer-events:auto}\n.dshm-shell header{min-width:0;padding-left:52px}\n.dshm-shell textarea{font-size:16px}\n.dshm-shell table{display:block;max-width:100%;overflow-x:auto}\n.dshm-shell pre{max-width:100%;overflow-x:auto}\n.dshm-shell img,.dshm-shell video,.dshm-shell canvas,.dshm-shell svg{max-width:100%}\n.dshm-shell [data-disclosure-row]{min-width:0;max-width:100%}\n.dshm-shell [data-disclosure-row]>*{min-width:0;overflow-wrap:anywhere}\n.dshm-shell [data-context-fields]>*{min-width:0}\n.dshm-shell [class*=\"_body\"]{max-width:100%;overflow-wrap:anywhere}\n.dshm-shell [data-question-key],.dshm-shell [data-plan-review-key]{box-sizing:border-box;width:100%;height:auto!important;min-width:0;flex:none!important;align-self:flex-end;padding:6px max(10px,env(safe-area-inset-left)) max(10px,env(safe-area-inset-bottom)) max(10px,env(safe-area-inset-right))!important}\n.dshm-shell [data-question-key]>section,.dshm-shell [data-plan-review-key]>section{width:100%;height:auto!important;min-height:0!important;max-width:none!important;max-height:min(68dvh,520px)!important;border-radius:16px!important}\n.dshm-shell [data-question-scroll],.dshm-shell [data-plan-review-scroll]{flex:0 1 auto!important;min-height:0!important;max-height:min(42dvh,360px)!important;overscroll-behavior:contain;scroll-padding-bottom:12px}\n@media(max-width:420px){.dshm-shell [data-context-fields]>*{display:grid;grid-template-columns:1fr!important;gap:4px}.dshm-shell [class*=\"_ioSection\"]{grid-template-columns:1fr!important}}\n@media(max-width:600px){\n.dshm-shell [data-question-key]>section>header{display:flex!important;visibility:visible!important;flex:none!important;gap:8px!important;padding:12px 8px 4px 14px!important}\n.dshm-shell [data-question-key]>section>header h2{min-width:0;overflow-wrap:anywhere;font-size:16px!important;line-height:22px!important}\n.dshm-shell [data-question-key]>section>header button{min-width:40px;min-height:40px}\n.dshm-shell [data-question-key] [role=radio],.dshm-shell [data-question-key] [role=checkbox]{min-height:48px!important;touch-action:manipulation}\n.dshm-shell [data-question-key]>section>footer{display:grid!important;grid-template-columns:auto minmax(0,1fr);align-items:center!important;flex:none!important;gap:6px 8px!important;margin-top:4px!important;padding:6px 10px 10px!important}\n.dshm-shell [data-question-key]>section>footer>:last-child{grid-column:1/-1;display:grid!important;grid-template-columns:repeat(2,minmax(0,1fr));width:100%;gap:8px!important}\n.dshm-shell [data-question-key]>section>footer>:last-child button{width:100%;min-height:44px}\n.dshm-shell [data-plan-review-key]>section>div:last-child{display:grid!important;grid-template-columns:1fr;gap:8px!important;padding:8px 12px 10px!important}\n.dshm-shell [data-plan-review-key]>section>div:last-child>div:last-child{display:grid!important;grid-template-columns:repeat(2,minmax(0,1fr));width:100%;gap:8px!important}\n.dshm-shell [data-plan-review-key]>section>div:last-child>div:last-child>button:first-child{grid-column:1/-1}\n.dshm-shell [data-plan-review-key]>section>div:last-child button{width:100%;min-height:44px}\n}\n@media(min-width:900px){\n.dshm-shell{grid-template-columns:auto minmax(0,1fr)}\n.dshm-main{grid-area:1/2}\n.dshm-drawer{position:static;grid-area:1/1;box-shadow:none}\n.dshm-drawer[data-open=true]{width:340px;box-shadow:none}\n.dshm-drawer[data-open=false]{width:56px}\n}\n@media(prefers-reduced-motion:reduce){.dshm-drawer,.dshm-details,.dshm-scrim,.dshm-drawer>*{transition:none!important}}\n`\n\nfunction MobileAppFrame(props: MobileRootProps & { readonly controller: MobileLayoutController }): ReactNode {\n const state = useSyncExternalStore(props.controller.subscribe, props.controller.getSnapshot)\n const suppressKeyboardUntil = useRef(0)\n const [wideViewport, setWideViewport] = useState(viewportIsWide)\n const [documentLanguage, setDocumentLanguage] = useState(document.documentElement.lang)\n const browserLanguages = navigator.languages.length > 0 ? navigator.languages : [navigator.language]\n const language = resolveMobileLayoutLanguage(documentLanguage, browserLanguages)\n const messages = MOBILE_LAYOUT_MESSAGES[language]\n const activeSessionId = props.useSessions(session => {\n const current = session.current\n return current !== undefined && session.byId[current]?.blank === false ? current : undefined\n })\n const hasSession = activeSessionId !== undefined\n\n useEffect(() => {\n if (typeof window.matchMedia !== 'function') return\n const query = window.matchMedia(`(min-width: ${WIDE_LAYOUT_MIN_WIDTH_PX}px)`)\n const syncViewport = (): void => { setWideViewport(query.matches) }\n syncViewport()\n query.addEventListener('change', syncViewport)\n return () => { query.removeEventListener('change', syncViewport) }\n }, [])\n\n useEffect(() => {\n const observer = new MutationObserver(() => { setDocumentLanguage(document.documentElement.lang) })\n observer.observe(document.documentElement, { attributes: true, attributeFilter: ['lang'] })\n return () => { observer.disconnect() }\n }, [])\n\n useEffect(() => {\n if (!hasSession) props.controller.closeDetails()\n }, [hasSession, props.controller])\n\n useEffect(() => {\n const suppressAutofocus = (event: FocusEvent): void => {\n if (performance.now() >= suppressKeyboardUntil.current) return\n const target = event.target\n if (target instanceof HTMLElement && (target.matches('input,textarea') || target.isContentEditable)) target.blur()\n }\n const suppressBranchAutofocus = (event: MouseEvent): void => {\n if (!(event.target instanceof Element)) return\n const branch = event.target.closest('button[aria-label*=\"分支\"],button[aria-label*=\"Branch\"],button[aria-label*=\"branch\"],button[aria-label*=\"Ramo\"],button[aria-label*=\"ramo\"]')\n if (branch === null || branch.hasAttribute('disabled') || branch.getAttribute('aria-disabled') === 'true') return\n suppressKeyboardUntil.current = performance.now() + 700\n window.setTimeout(() => {\n const active = document.activeElement\n if (active instanceof HTMLElement && (active.matches('input,textarea') || active.isContentEditable)) active.blur()\n }, 0)\n }\n const suppressCommandAutofocus = (event: MouseEvent): void => {\n if (!(event.target instanceof Element)) return\n const commandButton = event.target.closest('button[aria-haspopup=\"listbox\"]')\n if (commandButton === null) return\n // The native composer deliberately preserves editor focus on mousedown;\n // mobile command menus should open without summoning the soft keyboard.\n suppressKeyboardUntil.current = performance.now() + 700\n event.preventDefault()\n event.stopPropagation()\n window.setTimeout(() => {\n const active = document.activeElement\n if (active instanceof HTMLElement && (active.matches('input,textarea') || active.isContentEditable)) active.blur()\n }, 0)\n }\n document.addEventListener('focusin', suppressAutofocus, true)\n document.addEventListener('click', suppressBranchAutofocus, true)\n document.addEventListener('mousedown', suppressCommandAutofocus, true)\n return () => {\n document.removeEventListener('focusin', suppressAutofocus, true)\n document.removeEventListener('click', suppressBranchAutofocus, true)\n document.removeEventListener('mousedown', suppressCommandAutofocus, true)\n }\n }, [])\n\n // A persistent wide sidebar stays put: selecting a session only dismisses\n // the narrow overlay drawer (and its soft-keyboard suppression).\n const closeDrawerAfterSessionAction = (event: { readonly target: EventTarget | null }): void => {\n if (viewportIsWide()) return\n if (!(event.target instanceof Element)) return\n const row = event.target.closest<HTMLElement>('[role=\"treeitem\"][aria-selected]')\n const action = event.target.closest('button,[role=\"button\"]')\n const startsSession = action?.matches('button[class*=\"_newSession\"],button[class*=\"_brand\"]')\n || /新建会话|新会话|new session|new conversation|nuova sessione|nuova conversazione/i.test(action?.getAttribute('aria-label') ?? '')\n if (row === null && !startsSession) return\n if (row !== null && action !== null && action !== row) return\n suppressKeyboardUntil.current = performance.now() + 500\n // Let the session row finish its own click handler before unmounting the drawer.\n window.setTimeout(() => {\n props.controller.closeSidebar()\n const active = document.activeElement\n if (active instanceof HTMLInputElement || active instanceof HTMLTextAreaElement) active.blur()\n }, 0)\n }\n\n return createElement('div', { className: 'dshm-shell', lang: language },\n createElement('main', { className: 'dshm-main', 'data-dsh-mobile-session': activeSessionId }, props.renderSlot('conversation', {})),\n createElement('button', {\n 'aria-label': messages.closePanels,\n className: 'dshm-scrim',\n // A persistent wide sidebar needs no dimming; the scrim only covers\n // the narrow overlay drawer and the details panel.\n 'data-open': state.detailsOpen || (state.sidebarOpen && !wideViewport),\n onClick: () => { state.detailsOpen ? props.controller.closeDetails() : props.controller.closeSidebar() },\n tabIndex: state.sidebarOpen || state.detailsOpen ? 0 : -1,\n type: 'button',\n }),\n createElement('aside', {\n 'aria-label': messages.workspaceNavigation,\n className: 'dshm-drawer',\n 'data-open': state.sidebarOpen,\n onClickCapture: closeDrawerAfterSessionAction,\n }, props.renderSlot('sidebar', {\n collapsed: !state.sidebarOpen,\n width: state.sidebarOpen ? 340 : 56,\n })),\n createElement('aside', {\n 'aria-hidden': !state.detailsOpen,\n className: 'dshm-details',\n 'data-open': state.detailsOpen,\n ...(state.detailsOpen ? {} : { inert: '' }),\n }, hasSession ? props.renderSlot('details', {}) : undefined),\n createElement('div', { className: 'dshm-overlay', 'data-shell-overlay': true }, props.renderSlot('shell.overlay', {})),\n )\n}\n\n// One controller per document: re-applying the layout (reconnect, tab\n// refocus) must not reset the user's explicit sidebar toggle.\nlet sharedController: MobileLayoutController | undefined\n\n/** Replace the desktop layout module on the authenticated mobile surface. */\nexport function apply(ctx: MobileClientContext): void {\n sharedController ??= new MobileLayoutController()\n const controller = sharedController\n ctx.effect(() => {\n const style = document.createElement('style')\n style.dataset.plugin = 'dsh-mobile-layout'\n style.textContent = MOBILE_LAYOUT_STYLES\n document.head.append(style)\n const disposeService = ctx.reflect.provide('layout', controller)\n const disposeRoot = ctx.slots.register({\n name: 'root',\n children: {\n sidebar: { kind: 'single', scope: 'root' },\n conversation: { kind: 'single', scope: 'session-maybe' },\n details: { kind: 'single', scope: 'session' },\n 'shell.overlay': { kind: 'list', scope: 'root' },\n },\n }, props => createElement(MobileAppFrame, { ...props, controller }))\n return () => {\n disposeRoot()\n void disposeService()\n style.remove()\n }\n }, 'dsh-mobile: dedicated root layout')\n\n ctx.effect(() => {\n const presenter = new ThemePresenter()\n presenter.apply(ctx.theme.getTheme())\n const off = ctx.on('theme/change', snapshot => { presenter.apply(snapshot) })\n return () => { off(); presenter.dispose() }\n }, 'dsh-mobile: theme presenter')\n}\n\n/** Preserve the official layout module's dependency ordering. */\nexport const inject: readonly string[] = ['slots', 'theme']\n"],"mappings":";;;;;;;;EAEA,MAAa,yBAAyB,OAAO,OAAO;GAClD,IAAI,OAAO,OAAO;IAAE,aAAa;IAAmB,qBAAqB;GAAwC,CAAC;GAClH,IAAI,OAAO,OAAO;IAAE,aAAa;IAAgB,qBAAqB;GAAmC,CAAC;GAC1G,IAAI,OAAO,OAAO;IAAE,aAAa;IAAQ,qBAAqB;GAAW,CAAC;EAC5E,CAAC;;;;;;;;ECsCD,MAAa,2BAA2B;EAExC,SAAgB,qBAAqB,eAAgC;GACnE,OAAO,iBAAA;EACT;;EAGA,SAAS,iBAA0B;GACjC,IAAI,OAAO,WAAW,eAAe,OAAO,OAAO,eAAe,YAAY,OAAO;GACrF,OAAO,OAAO,WAAW,oBAA4C,CAAC,CAAC;EACzE;;EAGA,SAAgB,4BACd,kBACA,kBACsB;GACtB,OAAO,CAAC,kBAAkB,GAAG,gBAAgB,CAAC,CAC3C,KAAI,UAAS,MAAM,KAAK,CAAC,CAAC,YAAY,CAAC,CAAC,MAAM,OAAO,CAAC,CAAC,EAAE,CAAC,CAC1D,MAAM,UAAyC,UAAU,QAAQ,UAAU,QAAQ,UAAU,IAAI,KAAK;EAC3G;EAEA,IAAM,yBAAN,MAA6B;GAI3B,WAAmC,OAAO,OAAO;IAAE,aAAa,eAAe;IAAG,aAAa;GAAM,CAAC;GACtG,4BAA6B,IAAI,IAAgB;GAEjD,aAAsB,aAAuC;IAC3D,KAAK,UAAU,IAAI,QAAQ;IAC3B,aAAa;KAAE,KAAK,UAAU,OAAO,QAAQ;IAAE;GACjD;GAEA,oBAA6C,KAAK;GAElD,gBAAsB;IACpB,KAAK,OAAO,EAAE,aAAa,CAAC,KAAK,SAAS,YAAY,CAAC;GACzD;GAEA,cAAoB;IAClB,KAAK,OAAO,EAAE,aAAa,KAAK,CAAC;GACnC;GAEA,eAAqB;IACnB,KAAK,OAAO,EAAE,aAAa,MAAM,CAAC;GACpC;GAEA,eAAqB;IACnB,KAAK,OAAO,EAAE,aAAa,MAAM,CAAC;GACpC;GAEA,OAAe,MAAqC;IAClD,MAAM,WAAW,OAAO,OAAO;KAAE,GAAG,KAAK;KAAU,GAAG;IAAK,CAAC;IAC5D,IAAI,SAAS,gBAAgB,KAAK,SAAS,eAAe,SAAS,gBAAgB,KAAK,SAAS,aAAa;IAC9G,KAAK,WAAW;IAChB,KAAK,MAAM,YAAY,KAAK,WAAW,SAAS;GAClD;EACF;EAEA,IAAM,iBAAN,MAAqB;GACnB,gBAAkC,CAAC;GACnC,OAAwB,SAAS,cAAc,MAAM;GAErD,cAAc;IACZ,KAAK,KAAK,OAAO;GACnB;GAEA,MAAM,UAA+B;IACnC,MAAM,SAAS,SAAS,OAAO;IAC/B,SAAS,gBAAgB,MAAM,cAAc;IAC7C,SAAS,KAAK,gBAAgB,sBAAsB,WAAW,MAAM;IACrE,KAAK,MAAM,QAAQ,KAAK,eAAe,SAAS,KAAK,MAAM,eAAe,IAAI;IAC9E,KAAK,gBAAgB,CAAC;IACtB,KAAK,MAAM,CAAC,MAAM,UAAU,OAAO,QAAQ,SAAS,OAAO,MAAM,GAAG;KAClE,SAAS,KAAK,MAAM,YAAY,MAAM,KAAK;KAC3C,KAAK,cAAc,KAAK,IAAI;IAC9B;IACA,KAAK,KAAK,UAAU,iBAAiB,SAAS,IAAI,CAAC,CAAC;IACpD,IAAI,CAAC,KAAK,KAAK,aAAa,SAAS,KAAK,OAAO,KAAK,IAAI;GAC5D;GAEA,UAAgB;IACd,SAAS,gBAAgB,MAAM,eAAe,cAAc;IAC5D,SAAS,KAAK,gBAAgB,oBAAoB;IAClD,KAAK,MAAM,QAAQ,KAAK,eAAe,SAAS,KAAK,MAAM,eAAe,IAAI;IAC9E,KAAK,KAAK,OAAO;GACnB;EACF;EAEA,MAAa,uBAAuB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAqDpC,SAAS,eAAe,OAAqF;GAC3G,MAAM,SAAA,GAAQA,MAAAA,qBAAAA,CAAqB,MAAM,WAAW,WAAW,MAAM,WAAW,WAAW;GAC3F,MAAM,yBAAA,GAAwBC,MAAAA,OAAAA,CAAO,CAAC;GACtC,MAAM,CAAC,cAAc,oBAAA,GAAmBC,MAAAA,SAAAA,CAAS,cAAc;GAC/D,MAAM,CAAC,kBAAkB,wBAAA,GAAuBA,MAAAA,SAAAA,CAAS,SAAS,gBAAgB,IAAI;GAEtF,MAAM,WAAW,4BAA4B,kBADpB,UAAU,UAAU,SAAS,IAAI,UAAU,YAAY,CAAC,UAAU,QAAQ,CACpB;GAC/E,MAAM,WAAW,uBAAuB;GACxC,MAAM,kBAAkB,MAAM,aAAY,YAAW;IACnD,MAAM,UAAU,QAAQ;IACxB,OAAO,YAAY,KAAA,KAAa,QAAQ,KAAK,QAAQ,EAAE,UAAU,QAAQ,UAAU,KAAA;GACrF,CAAC;GACD,MAAM,aAAa,oBAAoB,KAAA;GAEvC,CAAA,GAAA,MAAA,UAAA,OAAgB;IACd,IAAI,OAAO,OAAO,eAAe,YAAY;IAC7C,MAAM,QAAQ,OAAO,WAAW,oBAA4C;IAC5E,MAAM,qBAA2B;KAAE,gBAAgB,MAAM,OAAO;IAAE;IAClE,aAAa;IACb,MAAM,iBAAiB,UAAU,YAAY;IAC7C,aAAa;KAAE,MAAM,oBAAoB,UAAU,YAAY;IAAE;GACnE,GAAG,CAAC,CAAC;GAEL,CAAA,GAAA,MAAA,UAAA,OAAgB;IACd,MAAM,WAAW,IAAI,uBAAuB;KAAE,oBAAoB,SAAS,gBAAgB,IAAI;IAAE,CAAC;IAClG,SAAS,QAAQ,SAAS,iBAAiB;KAAE,YAAY;KAAM,iBAAiB,CAAC,MAAM;IAAE,CAAC;IAC1F,aAAa;KAAE,SAAS,WAAW;IAAE;GACvC,GAAG,CAAC,CAAC;GAEL,CAAA,GAAA,MAAA,UAAA,OAAgB;IACd,IAAI,CAAC,YAAY,MAAM,WAAW,aAAa;GACjD,GAAG,CAAC,YAAY,MAAM,UAAU,CAAC;GAEjC,CAAA,GAAA,MAAA,UAAA,OAAgB;IACd,MAAM,qBAAqB,UAA4B;KACrD,IAAI,YAAY,IAAI,KAAK,sBAAsB,SAAS;KACxD,MAAM,SAAS,MAAM;KACrB,IAAI,kBAAkB,gBAAgB,OAAO,QAAQ,gBAAgB,KAAK,OAAO,oBAAoB,OAAO,KAAK;IACnH;IACA,MAAM,2BAA2B,UAA4B;KAC3D,IAAI,EAAE,MAAM,kBAAkB,UAAU;KACxC,MAAM,SAAS,MAAM,OAAO,QAAQ,oJAA0I;KAC9K,IAAI,WAAW,QAAQ,OAAO,aAAa,UAAU,KAAK,OAAO,aAAa,eAAe,MAAM,QAAQ;KAC3G,sBAAsB,UAAU,YAAY,IAAI,IAAI;KACpD,OAAO,iBAAiB;MACtB,MAAM,SAAS,SAAS;MACxB,IAAI,kBAAkB,gBAAgB,OAAO,QAAQ,gBAAgB,KAAK,OAAO,oBAAoB,OAAO,KAAK;KACnH,GAAG,CAAC;IACN;IACA,MAAM,4BAA4B,UAA4B;KAC5D,IAAI,EAAE,MAAM,kBAAkB,UAAU;KAExC,IADsB,MAAM,OAAO,QAAQ,mCAC3B,MAAM,MAAM;KAG5B,sBAAsB,UAAU,YAAY,IAAI,IAAI;KACpD,MAAM,eAAe;KACrB,MAAM,gBAAgB;KACtB,OAAO,iBAAiB;MACtB,MAAM,SAAS,SAAS;MACxB,IAAI,kBAAkB,gBAAgB,OAAO,QAAQ,gBAAgB,KAAK,OAAO,oBAAoB,OAAO,KAAK;KACnH,GAAG,CAAC;IACN;IACA,SAAS,iBAAiB,WAAW,mBAAmB,IAAI;IAC5D,SAAS,iBAAiB,SAAS,yBAAyB,IAAI;IAChE,SAAS,iBAAiB,aAAa,0BAA0B,IAAI;IACrE,aAAa;KACX,SAAS,oBAAoB,WAAW,mBAAmB,IAAI;KAC/D,SAAS,oBAAoB,SAAS,yBAAyB,IAAI;KACnE,SAAS,oBAAoB,aAAa,0BAA0B,IAAI;IAC1E;GACF,GAAG,CAAC,CAAC;GAIL,MAAM,iCAAiC,UAAyD;IAC9F,IAAI,eAAe,GAAG;IACtB,IAAI,EAAE,MAAM,kBAAkB,UAAU;IACxC,MAAM,MAAM,MAAM,OAAO,QAAqB,oCAAkC;IAChF,MAAM,SAAS,MAAM,OAAO,QAAQ,0BAAwB;IAC5D,MAAM,gBAAgB,QAAQ,QAAQ,0DAAsD,KACvF,4EAA4E,KAAK,QAAQ,aAAa,YAAY,KAAK,EAAE;IAC9H,IAAI,QAAQ,QAAQ,CAAC,eAAe;IACpC,IAAI,QAAQ,QAAQ,WAAW,QAAQ,WAAW,KAAK;IACvD,sBAAsB,UAAU,YAAY,IAAI,IAAI;IAEpD,OAAO,iBAAiB;KACtB,MAAM,WAAW,aAAa;KAC9B,MAAM,SAAS,SAAS;KACxB,IAAI,kBAAkB,oBAAoB,kBAAkB,qBAAqB,OAAO,KAAK;IAC/F,GAAG,CAAC;GACN;GAEA,QAAA,GAAOC,MAAAA,cAAAA,CAAc,OAAO;IAAE,WAAW;IAAc,MAAM;GAAS,IAAA,GACpEA,MAAAA,cAAAA,CAAc,QAAQ;IAAE,WAAW;IAAa,2BAA2B;GAAgB,GAAG,MAAM,WAAW,gBAAgB,CAAC,CAAC,CAAC,IAAA,GAClIA,MAAAA,cAAAA,CAAc,UAAU;IACtB,cAAc,SAAS;IACvB,WAAW;IAGX,aAAa,MAAM,eAAgB,MAAM,eAAe,CAAC;IACzD,eAAe;KAAE,MAAM,cAAc,MAAM,WAAW,aAAa,IAAI,MAAM,WAAW,aAAa;IAAE;IACvG,UAAU,MAAM,eAAe,MAAM,cAAc,IAAI;IACvD,MAAM;GACR,CAAC,IAAA,GACDA,MAAAA,cAAAA,CAAc,SAAS;IACrB,cAAc,SAAS;IACvB,WAAW;IACX,aAAa,MAAM;IACnB,gBAAgB;GAClB,GAAG,MAAM,WAAW,WAAW;IAC7B,WAAW,CAAC,MAAM;IAClB,OAAO,MAAM,cAAc,MAAM;GACnC,CAAC,CAAC,IAAA,GACFA,MAAAA,cAAAA,CAAc,SAAS;IACrB,eAAe,CAAC,MAAM;IACtB,WAAW;IACX,aAAa,MAAM;IACnB,GAAI,MAAM,cAAc,CAAC,IAAI,EAAE,OAAO,GAAG;GAC3C,GAAG,aAAa,MAAM,WAAW,WAAW,CAAC,CAAC,IAAI,KAAA,CAAS,IAAA,GAC3DA,MAAAA,cAAAA,CAAc,OAAO;IAAE,WAAW;IAAgB,sBAAsB;GAAK,GAAG,MAAM,WAAW,iBAAiB,CAAC,CAAC,CAAC,CACvH;EACF;EAIA,IAAI;;EAGJ,SAAgB,MAAM,KAAgC;GACpD,qBAAqB,IAAI,uBAAuB;GAChD,MAAM,aAAa;GACnB,IAAI,aAAa;IACf,MAAM,QAAQ,SAAS,cAAc,OAAO;IAC5C,MAAM,QAAQ,SAAS;IACvB,MAAM,cAAc;IACpB,SAAS,KAAK,OAAO,KAAK;IAC1B,MAAM,iBAAiB,IAAI,QAAQ,QAAQ,UAAU,UAAU;IAC/D,MAAM,cAAc,IAAI,MAAM,SAAS;KACrC,MAAM;KACN,UAAU;MACR,SAAS;OAAE,MAAM;OAAU,OAAO;MAAO;MACzC,cAAc;OAAE,MAAM;OAAU,OAAO;MAAgB;MACvD,SAAS;OAAE,MAAM;OAAU,OAAO;MAAU;MAC5C,iBAAiB;OAAE,MAAM;OAAQ,OAAO;MAAO;KACjD;IACF,IAAG,WAAA,GAASA,MAAAA,cAAAA,CAAc,gBAAgB;KAAE,GAAG;KAAO;IAAW,CAAC,CAAC;IACnE,aAAa;KACX,YAAY;KACZ,eAAoB;KACpB,MAAM,OAAO;IACf;GACF,GAAG,mCAAmC;GAEtC,IAAI,aAAa;IACf,MAAM,YAAY,IAAI,eAAe;IACrC,UAAU,MAAM,IAAI,MAAM,SAAS,CAAC;IACpC,MAAM,MAAM,IAAI,GAAG,iBAAgB,aAAY;KAAE,UAAU,MAAM,QAAQ;IAAE,CAAC;IAC5E,aAAa;KAAE,IAAI;KAAG,UAAU,QAAQ;IAAE;GAC5C,GAAG,6BAA6B;EAClC;;EAGA,MAAa,SAA4B,CAAC,SAAS,OAAO"}
1
+ {"version":3,"file":"mobile-layout.js","names":["useSyncExternalStore","useRef","useState","createElement"],"sources":["../src/mobile-layout-messages.ts","../src/mobile-layout.ts"],"sourcesContent":["export type MobileLayoutLanguage = 'it' | 'en' | 'zh'\n\nexport const MOBILE_LAYOUT_MESSAGES = Object.freeze({\n it: Object.freeze({ closePanels: 'Chiudi pannelli', workspaceNavigation: 'Navigazione area di lavoro e sessioni' }),\n en: Object.freeze({ closePanels: 'Close panels', workspaceNavigation: 'Workspace and session navigation' }),\n zh: Object.freeze({ closePanels: '关闭浮层', workspaceNavigation: '工作区与会话导航' }),\n})\n","import { createElement, useEffect, useRef, useState, useSyncExternalStore } from 'react'\nimport type { ReactNode } from 'react'\nimport { MOBILE_LAYOUT_MESSAGES, type MobileLayoutLanguage } from './mobile-layout-messages.js'\n\nexport { MOBILE_LAYOUT_MESSAGES } from './mobile-layout-messages.js'\nexport type { MobileLayoutLanguage } from './mobile-layout-messages.js'\n\ninterface SessionState {\n readonly current?: string\n readonly byId: Readonly<Record<string, { readonly blank?: boolean; readonly title?: string } | undefined>>\n}\n\ninterface MobileRootProps {\n readonly renderSlot: (name: string, owner: Record<string, unknown>) => ReactNode\n readonly useSessions: <T>(selector: (state: SessionState) => T) => T\n}\n\ninterface MobileClientContext {\n readonly effect: (effect: () => void | (() => void), label?: string) => void\n readonly on: (event: string, listener: (value: ThemeSnapshot) => void) => () => void\n readonly reflect: { provide: (name: string, value: unknown) => () => void | Promise<void> }\n readonly slots: {\n register: (options: Record<string, unknown>, component: (props: MobileRootProps) => ReactNode) => () => void\n }\n readonly theme: { getTheme: () => ThemeSnapshot }\n}\n\ninterface ThemeSnapshot {\n readonly active: {\n readonly colorScheme: 'dark' | 'light'\n readonly tokens: Readonly<Record<string, string>>\n }\n}\n\ninterface LayoutSnapshot {\n readonly sidebarOpen: boolean\n readonly detailsOpen: boolean\n}\n\n/**\n * Viewport width at which the dedicated layout treats the sidebar as a\n * persistent desktop panel instead of an overlay drawer. Narrow screens\n * keep the overlay behavior byte-for-byte.\n */\nexport const WIDE_LAYOUT_MIN_WIDTH_PX = 900\n\nexport function isWideViewportLayout(viewportWidth: number): boolean {\n return viewportWidth >= WIDE_LAYOUT_MIN_WIDTH_PX\n}\n\n/** Reads the live viewport; unknown environments (SSR, tests) stay narrow. */\nfunction viewportIsWide(): boolean {\n if (typeof window === 'undefined' || typeof window.matchMedia !== 'function') return false\n return window.matchMedia(`(min-width: ${WIDE_LAYOUT_MIN_WIDTH_PX}px)`).matches\n}\n\n/** Resolve the supported language used by the dedicated mobile layout. */\nexport function resolveMobileLayoutLanguage(\n documentLanguage: string,\n browserLanguages: readonly string[],\n): MobileLayoutLanguage {\n return [documentLanguage, ...browserLanguages]\n .map(value => value.trim().toLowerCase().split(/[-_]/u)[0])\n .find((value): value is MobileLayoutLanguage => value === 'it' || value === 'en' || value === 'zh') ?? 'en'\n}\n\nclass MobileLayoutController {\n // Wide viewports start with the persistent sidebar open; applying the\n // layout again (reconnect, refocus) reuses the module singleton below,\n // so an explicit user collapse is never reset.\n private snapshot: LayoutSnapshot = Object.freeze({ sidebarOpen: viewportIsWide(), detailsOpen: false })\n private readonly listeners = new Set<() => void>()\n\n readonly subscribe = (listener: () => void): (() => void) => {\n this.listeners.add(listener)\n return () => { this.listeners.delete(listener) }\n }\n\n readonly getSnapshot = (): LayoutSnapshot => this.snapshot\n\n toggleSidebar(): void {\n this.update({ sidebarOpen: !this.snapshot.sidebarOpen })\n }\n\n openDetails(): void {\n this.update({ detailsOpen: true })\n }\n\n closeDetails(): void {\n this.update({ detailsOpen: false })\n }\n\n closeSidebar(): void {\n this.update({ sidebarOpen: false })\n }\n\n private update(next: Partial<LayoutSnapshot>): void {\n const snapshot = Object.freeze({ ...this.snapshot, ...next })\n if (snapshot.sidebarOpen === this.snapshot.sidebarOpen && snapshot.detailsOpen === this.snapshot.detailsOpen) return\n this.snapshot = snapshot\n for (const listener of this.listeners) listener()\n }\n}\n\nclass ThemePresenter {\n private appliedTokens: string[] = []\n private readonly meta = document.createElement('meta')\n\n constructor() {\n this.meta.name = 'theme-color'\n }\n\n apply(snapshot: ThemeSnapshot): void {\n const scheme = snapshot.active.colorScheme\n document.documentElement.style.colorScheme = scheme\n document.body.toggleAttribute('data-ds-dark-theme', scheme === 'dark')\n for (const name of this.appliedTokens) document.body.style.removeProperty(name)\n this.appliedTokens = []\n for (const [name, value] of Object.entries(snapshot.active.tokens)) {\n document.body.style.setProperty(name, value)\n this.appliedTokens.push(name)\n }\n this.meta.content = getComputedStyle(document.body).backgroundColor\n if (!this.meta.isConnected) document.head.append(this.meta)\n }\n\n dispose(): void {\n document.documentElement.style.removeProperty('color-scheme')\n document.body.removeAttribute('data-ds-dark-theme')\n for (const name of this.appliedTokens) document.body.style.removeProperty(name)\n this.meta.remove()\n }\n}\n\nexport const MOBILE_LAYOUT_STYLES = `\nhtml,body,#root{width:100%;height:100%;overflow:hidden}\n.dshm-shell{position:relative;display:grid;width:100%;height:100dvh;min-width:0;overflow:hidden;background:var(--dsw-alias-bg-base,#fff)}\n.dshm-main{grid-area:1/1;min-width:0;min-height:0;overflow:hidden}\n.dshm-main>*,.dshm-main>*>*{min-width:0}\n.dshm-drawer{position:fixed;z-index:70;inset:0 auto 0 0;box-sizing:border-box;width:56px;max-width:100%;padding-top:env(safe-area-inset-top);overflow:hidden;background:var(--dsw-alias-bg-layer-1,#f8fafc);box-shadow:none;will-change:width;transition:width 240ms cubic-bezier(.22,1,.36,1),box-shadow 240ms ease}\n.dshm-drawer[data-open=true]{width:min(88vw,340px);box-shadow:18px 0 46px rgb(15 23 42 / 18%)}\n.dshm-drawer[data-open=false]{pointer-events:auto;visibility:visible}\n.dshm-drawer>*{width:100%!important;height:100%!important;transform:translateX(-6px);opacity:.94;transition:transform 220ms cubic-bezier(.22,1,.36,1),opacity 160ms ease-out}\n.dshm-drawer[data-open=true]>*{transform:translateX(0);opacity:1}\n.dshm-drawer[data-open=false]>*{width:56px!important}\n.dshm-details{position:fixed;z-index:80;inset:0 0 0 auto;box-sizing:border-box;width:min(94vw,460px);max-width:100%;padding-top:env(safe-area-inset-top);overflow:hidden;background:var(--dsw-alias-bg-layer-1,#fff);box-shadow:-18px 0 46px rgb(15 23 42 / 18%);transform:translateX(104%);transition:transform 190ms cubic-bezier(.22,1,.36,1)}\n.dshm-details[data-open=true]{transform:translateX(0)}\n.dshm-details[data-open=false]{pointer-events:none;visibility:hidden;transition:transform 190ms cubic-bezier(.22,1,.36,1),visibility 0s linear 190ms}\n.dshm-scrim{position:fixed;z-index:65;inset:0;border:0;background:rgb(15 23 42 / 40%);opacity:0;pointer-events:none;transition:opacity 180ms ease-out}\n.dshm-scrim[data-open=true]{opacity:1;pointer-events:auto}\n.dshm-overlay{position:fixed;z-index:90;inset:0;pointer-events:none}.dshm-overlay>*{pointer-events:auto}\n.dshm-shell header{min-width:0;padding-left:52px}\n.dshm-shell textarea{font-size:16px}\n.dshm-shell table{display:block;max-width:100%;overflow-x:auto}\n.dshm-shell pre{max-width:100%;overflow-x:auto}\n.dshm-shell img,.dshm-shell video,.dshm-shell canvas,.dshm-shell svg{max-width:100%}\n.dshm-shell [data-disclosure-row]{min-width:0;max-width:100%}\n.dshm-shell [data-disclosure-row]>*{min-width:0;overflow-wrap:anywhere}\n.dshm-shell [data-context-fields]>*{min-width:0}\n.dshm-shell [class*=\"_body\"]{max-width:100%;overflow-wrap:anywhere}\n.dshm-shell [data-question-key],.dshm-shell [data-plan-review-key]{box-sizing:border-box;width:100%;height:auto!important;min-width:0;flex:none!important;align-self:flex-end;padding:6px max(10px,env(safe-area-inset-left)) max(10px,env(safe-area-inset-bottom)) max(10px,env(safe-area-inset-right))!important}\n.dshm-shell [data-question-key]>section,.dshm-shell [data-plan-review-key]>section{width:100%;height:auto!important;min-height:0!important;max-width:none!important;max-height:min(68dvh,520px)!important;border-radius:16px!important}\n.dshm-shell [data-question-scroll],.dshm-shell [data-plan-review-scroll]{flex:0 1 auto!important;min-height:0!important;max-height:min(42dvh,360px)!important;overscroll-behavior:contain;scroll-padding-bottom:12px}\n@media(max-width:420px){.dshm-shell [data-context-fields]>*{display:grid;grid-template-columns:1fr!important;gap:4px}.dshm-shell [class*=\"_ioSection\"]{grid-template-columns:1fr!important}}\n@media(max-width:600px){\n.dshm-shell [data-question-key]>section>header{display:flex!important;visibility:visible!important;flex:none!important;gap:8px!important;padding:12px 8px 4px 14px!important}\n.dshm-shell [data-question-key]>section>header h2{min-width:0;overflow-wrap:anywhere;font-size:16px!important;line-height:22px!important}\n.dshm-shell [data-question-key]>section>header button{min-width:40px;min-height:40px}\n.dshm-shell [data-question-key] [role=radio],.dshm-shell [data-question-key] [role=checkbox]{min-height:48px!important;touch-action:manipulation}\n.dshm-shell [data-question-key]>section>footer{display:grid!important;grid-template-columns:auto minmax(0,1fr);align-items:center!important;flex:none!important;gap:6px 8px!important;margin-top:4px!important;padding:6px 10px 10px!important}\n.dshm-shell [data-question-key]>section>footer>:last-child{grid-column:1/-1;display:grid!important;grid-template-columns:repeat(2,minmax(0,1fr));width:100%;gap:8px!important}\n.dshm-shell [data-question-key]>section>footer>:last-child button{width:100%;min-height:44px}\n.dshm-shell [data-plan-review-key]>section>div:last-child{display:grid!important;grid-template-columns:1fr;gap:8px!important;padding:8px 12px 10px!important}\n.dshm-shell [data-plan-review-key]>section>div:last-child>div:last-child{display:grid!important;grid-template-columns:repeat(2,minmax(0,1fr));width:100%;gap:8px!important}\n.dshm-shell [data-plan-review-key]>section>div:last-child>div:last-child>button:first-child{grid-column:1/-1}\n.dshm-shell [data-plan-review-key]>section>div:last-child button{width:100%;min-height:44px}\n}\n@media(min-width:900px){\n.dshm-shell{grid-template-columns:auto minmax(0,1fr)}\n.dshm-main{grid-area:1/2}\n.dshm-drawer{position:static;grid-area:1/1;box-shadow:none}\n.dshm-drawer[data-open=true]{width:340px;box-shadow:none}\n.dshm-drawer[data-open=false]{width:56px}\n}\n@media(prefers-reduced-motion:reduce){.dshm-drawer,.dshm-details,.dshm-scrim,.dshm-drawer>*{transition:none!important}}\n`\n\nfunction MobileAppFrame(props: MobileRootProps & { readonly controller: MobileLayoutController }): ReactNode {\n const state = useSyncExternalStore(props.controller.subscribe, props.controller.getSnapshot)\n const suppressKeyboardUntil = useRef(0)\n const [wideViewport, setWideViewport] = useState(viewportIsWide)\n const [documentLanguage, setDocumentLanguage] = useState(document.documentElement.lang)\n const browserLanguages = navigator.languages.length > 0 ? navigator.languages : [navigator.language]\n const language = resolveMobileLayoutLanguage(documentLanguage, browserLanguages)\n const messages = MOBILE_LAYOUT_MESSAGES[language]\n const activeSessionId = props.useSessions(session => {\n const current = session.current\n return current !== undefined && session.byId[current]?.blank === false ? current : undefined\n })\n const hasSession = activeSessionId !== undefined\n\n useEffect(() => {\n if (typeof window.matchMedia !== 'function') return\n const query = window.matchMedia(`(min-width: ${WIDE_LAYOUT_MIN_WIDTH_PX}px)`)\n const syncViewport = (): void => { setWideViewport(query.matches) }\n syncViewport()\n query.addEventListener('change', syncViewport)\n return () => { query.removeEventListener('change', syncViewport) }\n }, [])\n\n useEffect(() => {\n const observer = new MutationObserver(() => { setDocumentLanguage(document.documentElement.lang) })\n observer.observe(document.documentElement, { attributes: true, attributeFilter: ['lang'] })\n return () => { observer.disconnect() }\n }, [])\n\n useEffect(() => {\n if (!hasSession) props.controller.closeDetails()\n }, [hasSession, props.controller])\n\n // Mirror the stock layout: project the current session title into the\n // browser tab, restoring the product title when no session is selected.\n const sessionTitle = props.useSessions(session =>\n session.current === undefined ? undefined : session.byId[session.current]?.title,\n )\n useEffect(() => {\n const productTitle = document.title\n if (sessionTitle !== undefined) document.title = `${sessionTitle} — ${productTitle}`\n return () => { document.title = productTitle }\n }, [sessionTitle])\n\n useEffect(() => {\n const suppressAutofocus = (event: FocusEvent): void => {\n if (performance.now() >= suppressKeyboardUntil.current) return\n const target = event.target\n if (target instanceof HTMLElement && (target.matches('input,textarea') || target.isContentEditable)) target.blur()\n }\n const suppressBranchAutofocus = (event: MouseEvent): void => {\n if (!(event.target instanceof Element)) return\n const branch = event.target.closest('button[aria-label*=\"分支\"],button[aria-label*=\"Branch\"],button[aria-label*=\"branch\"],button[aria-label*=\"Ramo\"],button[aria-label*=\"ramo\"]')\n if (branch === null || branch.hasAttribute('disabled') || branch.getAttribute('aria-disabled') === 'true') return\n suppressKeyboardUntil.current = performance.now() + 700\n window.setTimeout(() => {\n const active = document.activeElement\n if (active instanceof HTMLElement && (active.matches('input,textarea') || active.isContentEditable)) active.blur()\n }, 0)\n }\n const suppressCommandAutofocus = (event: MouseEvent): void => {\n if (!(event.target instanceof Element)) return\n const commandButton = event.target.closest('button[aria-haspopup=\"listbox\"]')\n if (commandButton === null) return\n // The native composer deliberately preserves editor focus on mousedown;\n // mobile command menus should open without summoning the soft keyboard.\n suppressKeyboardUntil.current = performance.now() + 700\n event.preventDefault()\n event.stopPropagation()\n window.setTimeout(() => {\n const active = document.activeElement\n if (active instanceof HTMLElement && (active.matches('input,textarea') || active.isContentEditable)) active.blur()\n }, 0)\n }\n document.addEventListener('focusin', suppressAutofocus, true)\n document.addEventListener('click', suppressBranchAutofocus, true)\n document.addEventListener('mousedown', suppressCommandAutofocus, true)\n return () => {\n document.removeEventListener('focusin', suppressAutofocus, true)\n document.removeEventListener('click', suppressBranchAutofocus, true)\n document.removeEventListener('mousedown', suppressCommandAutofocus, true)\n }\n }, [])\n\n // A persistent wide sidebar stays put: selecting a session only dismisses\n // the narrow overlay drawer (and its soft-keyboard suppression).\n const closeDrawerAfterSessionAction = (event: { readonly target: EventTarget | null }): void => {\n if (viewportIsWide()) return\n if (!(event.target instanceof Element)) return\n const row = event.target.closest<HTMLElement>('[role=\"treeitem\"][aria-selected]')\n const action = event.target.closest('button,[role=\"button\"]')\n const startsSession = action?.matches('button[class*=\"_newSession\"],button[class*=\"_brand\"]')\n || /新建会话|新会话|new session|new conversation|nuova sessione|nuova conversazione/i.test(action?.getAttribute('aria-label') ?? '')\n if (row === null && !startsSession) return\n if (row !== null && action !== null && action !== row) return\n suppressKeyboardUntil.current = performance.now() + 500\n // Let the session row finish its own click handler before unmounting the drawer.\n window.setTimeout(() => {\n props.controller.closeSidebar()\n const active = document.activeElement\n if (active instanceof HTMLInputElement || active instanceof HTMLTextAreaElement) active.blur()\n }, 0)\n }\n\n return createElement('div', { className: 'dshm-shell', lang: language },\n createElement('main', { className: 'dshm-main', 'data-dsh-mobile-session': activeSessionId }, props.renderSlot('conversation', {})),\n createElement('button', {\n 'aria-label': messages.closePanels,\n className: 'dshm-scrim',\n // A persistent wide sidebar needs no dimming; the scrim only covers\n // the narrow overlay drawer and the details panel.\n 'data-open': state.detailsOpen || (state.sidebarOpen && !wideViewport),\n onClick: () => { state.detailsOpen ? props.controller.closeDetails() : props.controller.closeSidebar() },\n tabIndex: state.sidebarOpen || state.detailsOpen ? 0 : -1,\n type: 'button',\n }),\n createElement('aside', {\n 'aria-label': messages.workspaceNavigation,\n className: 'dshm-drawer',\n 'data-open': state.sidebarOpen,\n onClickCapture: closeDrawerAfterSessionAction,\n }, props.renderSlot('sidebar', {\n collapsed: !state.sidebarOpen,\n width: state.sidebarOpen ? 340 : 56,\n })),\n createElement('aside', {\n 'aria-hidden': !state.detailsOpen,\n className: 'dshm-details',\n 'data-open': state.detailsOpen,\n ...(state.detailsOpen ? {} : { inert: '' }),\n }, hasSession ? props.renderSlot('details', {}) : undefined),\n createElement('div', { className: 'dshm-overlay', 'data-shell-overlay': true }, props.renderSlot('shell.overlay', {})),\n )\n}\n\n// One controller per document: re-applying the layout (reconnect, tab\n// refocus) must not reset the user's explicit sidebar toggle.\nlet sharedController: MobileLayoutController | undefined\n\n/** Replace the desktop layout module on the authenticated mobile surface. */\nexport function apply(ctx: MobileClientContext): void {\n sharedController ??= new MobileLayoutController()\n const controller = sharedController\n ctx.effect(() => {\n const style = document.createElement('style')\n style.dataset.plugin = 'dsh-mobile-layout'\n style.textContent = MOBILE_LAYOUT_STYLES\n document.head.append(style)\n const disposeService = ctx.reflect.provide('layout', controller)\n const disposeRoot = ctx.slots.register({\n name: 'root',\n children: {\n sidebar: { kind: 'single', scope: 'root' },\n conversation: { kind: 'single', scope: 'session-maybe' },\n details: { kind: 'single', scope: 'session' },\n 'shell.overlay': { kind: 'list', scope: 'root' },\n },\n }, props => createElement(MobileAppFrame, { ...props, controller }))\n return () => {\n disposeRoot()\n void disposeService()\n style.remove()\n }\n }, 'dsh-mobile: dedicated root layout')\n\n ctx.effect(() => {\n const presenter = new ThemePresenter()\n presenter.apply(ctx.theme.getTheme())\n const off = ctx.on('theme/change', snapshot => { presenter.apply(snapshot) })\n return () => { off(); presenter.dispose() }\n }, 'dsh-mobile: theme presenter')\n}\n\n/** Preserve the official layout module's dependency ordering. */\nexport const inject: readonly string[] = ['slots', 'theme']\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-mobile",
3
- "version": "0.3.10",
3
+ "version": "0.3.11",
4
4
  "private": false,
5
5
  "description": "DeepSeek Harness 移动端适配与安全访问插件,支持局域网、远程连接、Android App 和手机浏览器。",
6
6
  "type": "module",