@superad/ui 0.1.1 → 0.1.2

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/es/Flex/utils.mjs CHANGED
@@ -1,8 +1,8 @@
1
1
  //#region src/Flex/utils.ts
2
2
  const DIRECTION_TO_CSS = {
3
- "horizontal": "row",
3
+ horizontal: "row",
4
4
  "horizontal-reverse": "row-reverse",
5
- "vertical": "column",
5
+ vertical: "column",
6
6
  "vertical-reverse": "column-reverse"
7
7
  };
8
8
  const getFlexDirection = (direction, isHorizontal) => {
@@ -1 +1 @@
1
- {"version":3,"file":"utils.mjs","names":[],"sources":["../../src/Flex/utils.ts"],"sourcesContent":["import type { FlexDirection } from './type';\n\nconst DEFAULT_PREFIX = 'superad';\n\nexport const getPrefix = (prefixCls?: string) => prefixCls || DEFAULT_PREFIX;\n\nconst DIRECTION_TO_CSS: Record<FlexDirection, string> = {\n 'horizontal': 'row',\n 'horizontal-reverse': 'row-reverse',\n 'vertical': 'column',\n 'vertical-reverse': 'column-reverse',\n};\n\nexport const getFlexDirection = (direction?: FlexDirection, isHorizontal?: boolean) => {\n if (isHorizontal) return 'row';\n return DIRECTION_TO_CSS[direction ?? 'vertical'];\n};\n\nconst SPACE_DISTRIBUTIONS = new Set(['space-between', 'space-around', 'space-evenly']);\n\nexport const isSpaceDistribution = (distribution?: string) =>\n distribution ? SPACE_DISTRIBUTIONS.has(distribution) : undefined;\n\nexport const isHorizontal = (direction?: FlexDirection, isHorizontalMode?: boolean) =>\n getFlexDirection(direction, isHorizontalMode) === 'row';\n\nexport const isVertical = (direction?: FlexDirection, isHorizontalMode?: boolean) =>\n getFlexDirection(direction, isHorizontalMode) === 'column';\n\n/** 数字一律按 px 处理,字符串原样透传。 */\nexport const getCssValue = (value: string | number | undefined) =>\n typeof value === 'number' ? `${value}px` : value;\n"],"mappings":";AAMA,MAAM,mBAAkD;CACtD,cAAc;CACd,sBAAsB;CACtB,YAAY;CACZ,oBAAoB;AACtB;AAEA,MAAa,oBAAoB,WAA2B,iBAA2B;CACrF,IAAI,cAAc,OAAO;CACzB,OAAO,iBAAiB,aAAa;AACvC;AAEA,MAAM,sCAAsB,IAAI,IAAI;CAAC;CAAiB;CAAgB;AAAc,CAAC;AAErF,MAAa,uBAAuB,iBAClC,eAAe,oBAAoB,IAAI,YAAY,IAAI,KAAA;AAEzD,MAAa,gBAAgB,WAA2B,qBACtD,iBAAiB,WAAW,gBAAgB,MAAM;;AAMpD,MAAa,eAAe,UAC1B,OAAO,UAAU,WAAW,GAAG,MAAM,MAAM"}
1
+ {"version":3,"file":"utils.mjs","names":[],"sources":["../../src/Flex/utils.ts"],"sourcesContent":["import type { FlexDirection } from './type';\n\nconst DEFAULT_PREFIX = 'superad';\n\nexport const getPrefix = (prefixCls?: string) => prefixCls || DEFAULT_PREFIX;\n\nconst DIRECTION_TO_CSS: Record<FlexDirection, string> = {\n horizontal: 'row',\n 'horizontal-reverse': 'row-reverse',\n vertical: 'column',\n 'vertical-reverse': 'column-reverse',\n};\n\nexport const getFlexDirection = (direction?: FlexDirection, isHorizontal?: boolean) => {\n if (isHorizontal) return 'row';\n return DIRECTION_TO_CSS[direction ?? 'vertical'];\n};\n\nconst SPACE_DISTRIBUTIONS = new Set(['space-between', 'space-around', 'space-evenly']);\n\nexport const isSpaceDistribution = (distribution?: string) =>\n distribution ? SPACE_DISTRIBUTIONS.has(distribution) : undefined;\n\nexport const isHorizontal = (direction?: FlexDirection, isHorizontalMode?: boolean) =>\n getFlexDirection(direction, isHorizontalMode) === 'row';\n\nexport const isVertical = (direction?: FlexDirection, isHorizontalMode?: boolean) =>\n getFlexDirection(direction, isHorizontalMode) === 'column';\n\n/** 数字一律按 px 处理,字符串原样透传。 */\nexport const getCssValue = (value: string | number | undefined) =>\n typeof value === 'number' ? `${value}px` : value;\n"],"mappings":";AAMA,MAAM,mBAAkD;CACtD,YAAY;CACZ,sBAAsB;CACtB,UAAU;CACV,oBAAoB;AACtB;AAEA,MAAa,oBAAoB,WAA2B,iBAA2B;CACrF,IAAI,cAAc,OAAO;CACzB,OAAO,iBAAiB,aAAa;AACvC;AAEA,MAAM,sCAAsB,IAAI,IAAI;CAAC;CAAiB;CAAgB;AAAc,CAAC;AAErF,MAAa,uBAAuB,iBAClC,eAAe,oBAAoB,IAAI,YAAY,IAAI,KAAA;AAEzD,MAAa,gBAAgB,WAA2B,qBACtD,iBAAiB,WAAW,gBAAgB,MAAM;;AAMpD,MAAa,eAAe,UAC1B,OAAO,UAAU,WAAW,GAAG,MAAM,MAAM"}
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","names":[],"sources":["../../src/GlobalFocusRing/index.ts"],"sourcesContent":["import { globalFocusRingCSS } from './style';\n\nconst RING_ATTRIBUTE = 'data-superad-global-focus-ring';\nconst MANAGED_ATTRIBUTE = 'data-superad-focus-ring';\nconst OPT_OUT_SELECTOR = '[data-superad-focus-ring=\"off\"]';\nconst ANCHOR_NAME = '--superad-global-focus';\nconst FALLBACK_RING_COLOR = '#1677ff';\n\n/** 这些 input 类型本身是可点按控件,需要焦点环;文本类输入不需要。 */\nconst RING_INPUT_TYPES = new Set([\n 'button',\n 'submit',\n 'reset',\n 'image',\n 'checkbox',\n 'radio',\n 'range',\n 'color',\n 'file',\n]);\n\n/** 当前环境是否支持锚点定位 + popover 这套实现。 */\nconst isSupported = (doc: Document): boolean => {\n const win = doc.defaultView;\n return Boolean(\n win?.CSS?.supports('top', 'anchor(top)') &&\n win.CSS.supports('position-visibility', 'anchors-visible') &&\n 'showPopover' in doc.createElement('div'),\n );\n};\n\n/** 该元素是否值得挂焦点环。 */\nconst isRingTarget = (element: Element | null, doc: Document): element is HTMLElement => {\n if (!element || element === doc.body) return false;\n if (element.namespaceURI !== 'http://www.w3.org/1999/xhtml') return false;\n if (element.shadowRoot) return false;\n if (element.closest(OPT_OUT_SELECTOR)) return false;\n if (element.matches('textarea')) return false;\n if ((element as HTMLElement).isContentEditable) return false;\n if (element.matches('input') && !RING_INPUT_TYPES.has((element as HTMLInputElement).type))\n return false;\n return element.matches(':focus-visible');\n};\n\ninterface InlineStyleSnapshot {\n priority: string;\n property: string;\n value: string;\n}\n\n/** 单个文档内的焦点环控制器:一个 ring 元素跟随当前 :focus-visible 元素。 */\nclass FocusRingController {\n private readonly doc: Document;\n private readonly win: Window;\n private readonly sheet: HTMLStyleElement;\n private readonly ring: HTMLDivElement;\n\n private target?: HTMLElement;\n private restoreTarget?: () => void;\n private frame = 0;\n private disposed = false;\n private readonly observer: MutationObserver;\n\n constructor(doc: Document) {\n this.doc = doc;\n this.win = doc.defaultView!;\n\n this.sheet = doc.createElement('style');\n this.sheet.textContent = globalFocusRingCSS;\n this.ring = doc.createElement('div');\n this.ring.setAttribute(RING_ATTRIBUTE, '');\n this.ring.setAttribute('aria-hidden', 'true');\n this.ring.setAttribute('popover', 'manual');\n doc.head.append(this.sheet);\n doc.body.append(this.ring);\n\n this.observer = new MutationObserver(() => {\n if (this.target && !this.target.isConnected) this.clear();\n });\n this.observer.observe(doc.body, { childList: true, subtree: true });\n\n doc.addEventListener('focusin', this.handleFocusIn, true);\n doc.addEventListener('focusout', this.schedule, true);\n doc.addEventListener('keydown', this.schedule, true);\n doc.addEventListener('pointerdown', this.schedule, true);\n this.win.addEventListener('blur', this.handleWindowBlur);\n this.win.addEventListener('focus', this.schedule);\n }\n\n dispose = () => {\n this.disposed = true;\n this.win.cancelAnimationFrame(this.frame);\n this.observer.disconnect();\n this.doc.removeEventListener('focusin', this.handleFocusIn, true);\n this.doc.removeEventListener('focusout', this.schedule, true);\n this.doc.removeEventListener('keydown', this.schedule, true);\n this.doc.removeEventListener('pointerdown', this.schedule, true);\n this.win.removeEventListener('blur', this.handleWindowBlur);\n this.win.removeEventListener('focus', this.schedule);\n this.clear();\n this.ring.remove();\n this.sheet.remove();\n };\n\n private handleFocusIn = () => this.win.queueMicrotask(() => this.sync());\n\n private handleWindowBlur = () => {\n this.win.cancelAnimationFrame(this.frame);\n this.frame = 0;\n if (this.ring.matches(':popover-open')) this.ring.hidePopover();\n };\n\n private schedule = () => {\n if (this.frame) return;\n this.frame = this.win.requestAnimationFrame(() => {\n this.frame = 0;\n this.sync();\n });\n };\n\n /** 安装后立即对齐一次当前焦点。 */\n syncNow = () => this.sync();\n\n private clear = () => {\n if (this.ring.matches(':popover-open')) this.ring.hidePopover();\n this.restoreTarget?.();\n this.restoreTarget = undefined;\n this.target = undefined;\n };\n\n /** 找到 ring 应该挂的父节点:向上跳过 contain: none 的祖先。 */\n private resolveRingParent(element: HTMLElement): HTMLElement {\n let scope = element.parentElement;\n while (scope && this.win.getComputedStyle(scope).contain === 'none')\n scope = scope.parentElement;\n return scope || this.doc.body;\n }\n\n /** 把目标元素的内联样式/标记改回去,仅当值仍是我们写入的那个。 */\n private static restoreInlineStyles(\n element: HTMLElement,\n saved: InlineStyleSnapshot[],\n assignedAnchor: string,\n oldMarker: string | null,\n ) {\n for (const { property, value, priority } of saved) {\n const assigned = property === 'anchor-name' ? assignedAnchor : 'none';\n if (element.style.getPropertyValue(property) !== assigned) continue;\n if (value) element.style.setProperty(property, value, priority);\n else element.style.removeProperty(property);\n }\n if (element.getAttribute(MANAGED_ATTRIBUTE) === 'managed') {\n if (oldMarker === null) element.removeAttribute(MANAGED_ATTRIBUTE);\n else element.setAttribute(MANAGED_ATTRIBUTE, oldMarker);\n }\n }\n\n private sync = () => {\n if (this.disposed) return;\n const next = this.doc.activeElement;\n\n const alreadyShowing =\n next === this.target &&\n next?.matches(':focus-visible') &&\n next.isConnected &&\n this.ring.matches(':popover-open');\n if (alreadyShowing) return;\n\n this.clear();\n if (!isRingTarget(next, this.doc)) return;\n\n const element = next;\n const ringParent = this.resolveRingParent(element);\n const moved = this.ring.parentElement !== ringParent;\n if (moved) ringParent.append(this.ring);\n\n const computed = this.win.getComputedStyle(element);\n const borderRadius = computed.borderRadius;\n const color =\n computed.getPropertyValue('--superad-focus-ring-color').trim() ||\n computed.getPropertyValue('--ant-color-info').trim() ||\n FALLBACK_RING_COLOR;\n const styleChanged =\n this.ring.style.borderRadius !== borderRadius ||\n this.ring.style.getPropertyValue('--superad-focus-ring-color') !== color;\n if (styleChanged) {\n this.ring.style.borderRadius = borderRadius;\n this.ring.style.setProperty('--superad-focus-ring-color', color);\n }\n // 刚移动或刚改样式时锚点还没生效,下一帧再同步\n if (moved || styleChanged) {\n this.schedule();\n return;\n }\n\n const saved: InlineStyleSnapshot[] = ['anchor-name', 'outline'].map((property) => ({\n priority: element.style.getPropertyPriority(property),\n property,\n value: element.style.getPropertyValue(property),\n }));\n const oldMarker = element.getAttribute(MANAGED_ATTRIBUTE);\n const existingAnchor = computed.getPropertyValue('anchor-name').trim();\n const assignedAnchor =\n existingAnchor && existingAnchor !== 'none'\n ? `${existingAnchor}, ${ANCHOR_NAME}`\n : ANCHOR_NAME;\n element.style.setProperty('anchor-name', assignedAnchor, 'important');\n\n this.restoreTarget = () =>\n FocusRingController.restoreInlineStyles(element, saved, assignedAnchor, oldMarker);\n\n const rect = element.getBoundingClientRect();\n const hasUsableBox =\n element.getClientRects().length === 1 &&\n Boolean(rect.width) &&\n Boolean(rect.height) &&\n computed.visibility === 'visible';\n if (!hasUsableBox) {\n this.clear();\n return;\n }\n\n try {\n this.ring.showPopover();\n const bounds = this.ring.getBoundingClientRect();\n const maxEdgeDelta = Math.max(\n ...(['top', 'right', 'bottom', 'left'] as const).map((edge) =>\n Math.abs(bounds[edge] - rect[edge]),\n ),\n );\n // 锚点没对齐(比如目标在 transform 容器里)就放弃,不显示错位的环\n if (maxEdgeDelta > 1) {\n this.clear();\n return;\n }\n element.style.setProperty('outline', 'none', 'important');\n element.setAttribute(MANAGED_ATTRIBUTE, 'managed');\n this.target = element;\n } catch {\n this.clear();\n }\n };\n}\n\nconst installations = new WeakMap<Document, { controller: FocusRingController; users: number }>();\n\n/** 每个 document 安装一次;返回的释放函数对嵌套 Provider 与 Strict Mode 安全。 */\nexport const installGlobalFocusRing = (doc: Document = document): (() => void) => {\n const existing = installations.get(doc);\n if (existing) {\n existing.users += 1;\n let released = false;\n return () => {\n if (released) return;\n released = true;\n existing.users -= 1;\n if (existing.users === 0) {\n existing.controller.dispose();\n installations.delete(doc);\n }\n };\n }\n\n if (!isSupported(doc) || !doc.body) return () => {};\n\n const controller = new FocusRingController(doc);\n installations.set(doc, { controller, users: 0 });\n controller.syncNow();\n // 走一遍上面的引用计数分支,拿到释放函数\n return installGlobalFocusRing(doc);\n};\n"],"mappings":";;AAEA,MAAM,iBAAiB;AACvB,MAAM,oBAAoB;AAC1B,MAAM,mBAAmB;AACzB,MAAM,cAAc;AACpB,MAAM,sBAAsB;;AAG5B,MAAM,mCAAmB,IAAI,IAAI;CAC/B;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;AACF,CAAC;;AAGD,MAAM,eAAe,QAA2B;CAC9C,MAAM,MAAM,IAAI;CAChB,OAAO,QACL,KAAK,KAAK,SAAS,OAAO,aAAa,KACvC,IAAI,IAAI,SAAS,uBAAuB,iBAAiB,KACzD,iBAAiB,IAAI,cAAc,KAAK,CAC1C;AACF;;AAGA,MAAM,gBAAgB,SAAyB,QAA0C;CACvF,IAAI,CAAC,WAAW,YAAY,IAAI,MAAM,OAAO;CAC7C,IAAI,QAAQ,iBAAiB,gCAAgC,OAAO;CACpE,IAAI,QAAQ,YAAY,OAAO;CAC/B,IAAI,QAAQ,QAAQ,gBAAgB,GAAG,OAAO;CAC9C,IAAI,QAAQ,QAAQ,UAAU,GAAG,OAAO;CACxC,IAAK,QAAwB,mBAAmB,OAAO;CACvD,IAAI,QAAQ,QAAQ,OAAO,KAAK,CAAC,iBAAiB,IAAK,QAA6B,IAAI,GACtF,OAAO;CACT,OAAO,QAAQ,QAAQ,gBAAgB;AACzC;;AASA,IAAM,sBAAN,MAAM,oBAAoB;CAYxB,YAAY,KAAe;EAJX,KAAA,QAAA;EACG,KAAA,WAAA;EA6BH,KAAA,gBAAA;GACd,KAAK,WAAW;GAChB,KAAK,IAAI,qBAAqB,KAAK,KAAK;GACxC,KAAK,SAAS,WAAW;GACzB,KAAK,IAAI,oBAAoB,WAAW,KAAK,eAAe,IAAI;GAChE,KAAK,IAAI,oBAAoB,YAAY,KAAK,UAAU,IAAI;GAC5D,KAAK,IAAI,oBAAoB,WAAW,KAAK,UAAU,IAAI;GAC3D,KAAK,IAAI,oBAAoB,eAAe,KAAK,UAAU,IAAI;GAC/D,KAAK,IAAI,oBAAoB,QAAQ,KAAK,gBAAgB;GAC1D,KAAK,IAAI,oBAAoB,SAAS,KAAK,QAAQ;GACnD,KAAK,MAAM;GACX,KAAK,KAAK,OAAO;GACjB,KAAK,MAAM,OAAO;EACpB;EAE8B,KAAA,sBAAA,KAAK,IAAI,qBAAqB,KAAK,KAAK,CAAC;EAEtC,KAAA,yBAAA;GAC/B,KAAK,IAAI,qBAAqB,KAAK,KAAK;GACxC,KAAK,QAAQ;GACb,IAAI,KAAK,KAAK,QAAQ,eAAe,GAAG,KAAK,KAAK,YAAY;EAChE;EAEyB,KAAA,iBAAA;GACvB,IAAI,KAAK,OAAO;GAChB,KAAK,QAAQ,KAAK,IAAI,4BAA4B;IAChD,KAAK,QAAQ;IACb,KAAK,KAAK;GACZ,CAAC;EACH;EAGgB,KAAA,gBAAA,KAAK,KAAK;EAEJ,KAAA,cAAA;GACpB,IAAI,KAAK,KAAK,QAAQ,eAAe,GAAG,KAAK,KAAK,YAAY;GAC9D,KAAK,gBAAgB;GACrB,KAAK,gBAAgB,KAAA;GACrB,KAAK,SAAS,KAAA;EAChB;EA6BqB,KAAA,aAAA;GACnB,IAAI,KAAK,UAAU;GACnB,MAAM,OAAO,KAAK,IAAI;GAOtB,IAJE,SAAS,KAAK,UACd,MAAM,QAAQ,gBAAgB,KAC9B,KAAK,eACL,KAAK,KAAK,QAAQ,eAAe,GACf;GAEpB,KAAK,MAAM;GACX,IAAI,CAAC,aAAa,MAAM,KAAK,GAAG,GAAG;GAEnC,MAAM,UAAU;GAChB,MAAM,aAAa,KAAK,kBAAkB,OAAO;GACjD,MAAM,QAAQ,KAAK,KAAK,kBAAkB;GAC1C,IAAI,OAAO,WAAW,OAAO,KAAK,IAAI;GAEtC,MAAM,WAAW,KAAK,IAAI,iBAAiB,OAAO;GAClD,MAAM,eAAe,SAAS;GAC9B,MAAM,QACJ,SAAS,iBAAiB,4BAA4B,CAAC,CAAC,KAAK,KAC7D,SAAS,iBAAiB,kBAAkB,CAAC,CAAC,KAAK,KACnD;GACF,MAAM,eACJ,KAAK,KAAK,MAAM,iBAAiB,gBACjC,KAAK,KAAK,MAAM,iBAAiB,4BAA4B,MAAM;GACrE,IAAI,cAAc;IAChB,KAAK,KAAK,MAAM,eAAe;IAC/B,KAAK,KAAK,MAAM,YAAY,8BAA8B,KAAK;GACjE;GAEA,IAAI,SAAS,cAAc;IACzB,KAAK,SAAS;IACd;GACF;GAEA,MAAM,QAA+B,CAAC,eAAe,SAAS,CAAC,CAAC,KAAK,cAAc;IACjF,UAAU,QAAQ,MAAM,oBAAoB,QAAQ;IACpD;IACA,OAAO,QAAQ,MAAM,iBAAiB,QAAQ;GAChD,EAAE;GACF,MAAM,YAAY,QAAQ,aAAa,iBAAiB;GACxD,MAAM,iBAAiB,SAAS,iBAAiB,aAAa,CAAC,CAAC,KAAK;GACrE,MAAM,iBACJ,kBAAkB,mBAAmB,SACjC,GAAG,eAAe,IAAI,gBACtB;GACN,QAAQ,MAAM,YAAY,eAAe,gBAAgB,WAAW;GAEpE,KAAK,sBACH,oBAAoB,oBAAoB,SAAS,OAAO,gBAAgB,SAAS;GAEnF,MAAM,OAAO,QAAQ,sBAAsB;GAM3C,IAAI,EAJF,QAAQ,eAAe,CAAC,CAAC,WAAW,KACpC,QAAQ,KAAK,KAAK,KAClB,QAAQ,KAAK,MAAM,KACnB,SAAS,eAAe,YACP;IACjB,KAAK,MAAM;IACX;GACF;GAEA,IAAI;IACF,KAAK,KAAK,YAAY;IACtB,MAAM,SAAS,KAAK,KAAK,sBAAsB;IAO/C,IANqB,KAAK,IACxB,GAAI;KAAC;KAAO;KAAS;KAAU;IAAM,CAAC,CAAW,KAAK,SACpD,KAAK,IAAI,OAAO,QAAQ,KAAK,KAAK,CACpC,CAGa,IAAI,GAAG;KACpB,KAAK,MAAM;KACX;IACF;IACA,QAAQ,MAAM,YAAY,WAAW,QAAQ,WAAW;IACxD,QAAQ,aAAa,mBAAmB,SAAS;IACjD,KAAK,SAAS;GAChB,QAAQ;IACN,KAAK,MAAM;GACb;EACF;EAjLE,KAAK,MAAM;EACX,KAAK,MAAM,IAAI;EAEf,KAAK,QAAQ,IAAI,cAAc,OAAO;EACtC,KAAK,MAAM,cAAc;EACzB,KAAK,OAAO,IAAI,cAAc,KAAK;EACnC,KAAK,KAAK,aAAa,gBAAgB,EAAE;EACzC,KAAK,KAAK,aAAa,eAAe,MAAM;EAC5C,KAAK,KAAK,aAAa,WAAW,QAAQ;EAC1C,IAAI,KAAK,OAAO,KAAK,KAAK;EAC1B,IAAI,KAAK,OAAO,KAAK,IAAI;EAEzB,KAAK,WAAW,IAAI,uBAAuB;GACzC,IAAI,KAAK,UAAU,CAAC,KAAK,OAAO,aAAa,KAAK,MAAM;EAC1D,CAAC;EACD,KAAK,SAAS,QAAQ,IAAI,MAAM;GAAE,WAAW;GAAM,SAAS;EAAK,CAAC;EAElE,IAAI,iBAAiB,WAAW,KAAK,eAAe,IAAI;EACxD,IAAI,iBAAiB,YAAY,KAAK,UAAU,IAAI;EACpD,IAAI,iBAAiB,WAAW,KAAK,UAAU,IAAI;EACnD,IAAI,iBAAiB,eAAe,KAAK,UAAU,IAAI;EACvD,KAAK,IAAI,iBAAiB,QAAQ,KAAK,gBAAgB;EACvD,KAAK,IAAI,iBAAiB,SAAS,KAAK,QAAQ;CAClD;;CA4CA,kBAA0B,SAAmC;EAC3D,IAAI,QAAQ,QAAQ;EACpB,OAAO,SAAS,KAAK,IAAI,iBAAiB,KAAK,CAAC,CAAC,YAAY,QAC3D,QAAQ,MAAM;EAChB,OAAO,SAAS,KAAK,IAAI;CAC3B;;CAGA,OAAe,oBACb,SACA,OACA,gBACA,WACA;EACA,KAAK,MAAM,EAAE,UAAU,OAAO,cAAc,OAAO;GACjD,MAAM,WAAW,aAAa,gBAAgB,iBAAiB;GAC/D,IAAI,QAAQ,MAAM,iBAAiB,QAAQ,MAAM,UAAU;GAC3D,IAAI,OAAO,QAAQ,MAAM,YAAY,UAAU,OAAO,QAAQ;QACzD,QAAQ,MAAM,eAAe,QAAQ;EAC5C;EACA,IAAI,QAAQ,aAAa,iBAAiB,MAAM,WAAW;GACzD,IAAI,cAAc,MAAM,QAAQ,gBAAgB,iBAAiB;QAC5D,QAAQ,aAAa,mBAAmB,SAAS;EACxD;CACF;AAuFF;AAEA,MAAM,gCAAgB,IAAI,QAAsE;;AAGhG,MAAa,0BAA0B,MAAgB,aAA2B;CAChF,MAAM,WAAW,cAAc,IAAI,GAAG;CACtC,IAAI,UAAU;EACZ,SAAS,SAAS;EAClB,IAAI,WAAW;EACf,aAAa;GACX,IAAI,UAAU;GACd,WAAW;GACX,SAAS,SAAS;GAClB,IAAI,SAAS,UAAU,GAAG;IACxB,SAAS,WAAW,QAAQ;IAC5B,cAAc,OAAO,GAAG;GAC1B;EACF;CACF;CAEA,IAAI,CAAC,YAAY,GAAG,KAAK,CAAC,IAAI,MAAM,aAAa,CAAC;CAElD,MAAM,aAAa,IAAI,oBAAoB,GAAG;CAC9C,cAAc,IAAI,KAAK;EAAE;EAAY,OAAO;CAAE,CAAC;CAC/C,WAAW,QAAQ;CAEnB,OAAO,uBAAuB,GAAG;AACnC"}
1
+ {"version":3,"file":"index.mjs","names":[],"sources":["../../src/GlobalFocusRing/index.ts"],"sourcesContent":["import { globalFocusRingCSS } from './style';\n\nconst RING_ATTRIBUTE = 'data-superad-global-focus-ring';\nconst MANAGED_ATTRIBUTE = 'data-superad-focus-ring';\nconst OPT_OUT_SELECTOR = '[data-superad-focus-ring=\"off\"]';\nconst ANCHOR_NAME = '--superad-global-focus';\nconst FALLBACK_RING_COLOR = '#1677ff';\n\n/** 这些 input 类型本身是可点按控件,需要焦点环;文本类输入不需要。 */\nconst RING_INPUT_TYPES = new Set([\n 'button',\n 'submit',\n 'reset',\n 'image',\n 'checkbox',\n 'radio',\n 'range',\n 'color',\n 'file',\n]);\n\n/** 当前环境是否支持锚点定位 + popover 这套实现。 */\nconst isSupported = (doc: Document): boolean => {\n const win = doc.defaultView;\n return Boolean(\n win?.CSS?.supports('top', 'anchor(top)') &&\n win.CSS.supports('position-visibility', 'anchors-visible') &&\n 'showPopover' in doc.createElement('div'),\n );\n};\n\n/** 该元素是否值得挂焦点环。 */\nconst isRingTarget = (element: Element | null, doc: Document): element is HTMLElement => {\n if (!element || element === doc.body) return false;\n if (element.namespaceURI !== 'http://www.w3.org/1999/xhtml') return false;\n if (element.shadowRoot) return false;\n if (element.closest(OPT_OUT_SELECTOR)) return false;\n if (element.matches('textarea')) return false;\n if ((element as HTMLElement).isContentEditable) return false;\n if (element.matches('input') && !RING_INPUT_TYPES.has((element as HTMLInputElement).type))\n return false;\n return element.matches(':focus-visible');\n};\n\ninterface InlineStyleSnapshot {\n priority: string;\n property: string;\n value: string;\n}\n\n/** 单个文档内的焦点环控制器:一个 ring 元素跟随当前 :focus-visible 元素。 */\nclass FocusRingController {\n private readonly doc: Document;\n private readonly win: Window;\n private readonly sheet: HTMLStyleElement;\n private readonly ring: HTMLDivElement;\n\n private target?: HTMLElement;\n private restoreTarget?: () => void;\n private frame = 0;\n private disposed = false;\n private readonly observer: MutationObserver;\n\n constructor(doc: Document) {\n this.doc = doc;\n this.win = doc.defaultView!;\n\n this.sheet = doc.createElement('style');\n this.sheet.textContent = globalFocusRingCSS;\n this.ring = doc.createElement('div');\n this.ring.setAttribute(RING_ATTRIBUTE, '');\n this.ring.setAttribute('aria-hidden', 'true');\n this.ring.setAttribute('popover', 'manual');\n doc.head.append(this.sheet);\n doc.body.append(this.ring);\n\n this.observer = new MutationObserver(() => {\n if (this.target && !this.target.isConnected) this.clear();\n });\n this.observer.observe(doc.body, { childList: true, subtree: true });\n\n doc.addEventListener('focusin', this.handleFocusIn, true);\n doc.addEventListener('focusout', this.schedule, true);\n doc.addEventListener('keydown', this.schedule, true);\n doc.addEventListener('pointerdown', this.schedule, true);\n this.win.addEventListener('blur', this.handleWindowBlur);\n this.win.addEventListener('focus', this.schedule);\n }\n\n dispose = () => {\n this.disposed = true;\n this.win.cancelAnimationFrame(this.frame);\n this.observer.disconnect();\n this.doc.removeEventListener('focusin', this.handleFocusIn, true);\n this.doc.removeEventListener('focusout', this.schedule, true);\n this.doc.removeEventListener('keydown', this.schedule, true);\n this.doc.removeEventListener('pointerdown', this.schedule, true);\n this.win.removeEventListener('blur', this.handleWindowBlur);\n this.win.removeEventListener('focus', this.schedule);\n this.clear();\n this.ring.remove();\n this.sheet.remove();\n };\n\n private handleFocusIn = () => this.win.queueMicrotask(() => this.sync());\n\n private handleWindowBlur = () => {\n this.win.cancelAnimationFrame(this.frame);\n this.frame = 0;\n if (this.ring.matches(':popover-open')) this.ring.hidePopover();\n };\n\n private schedule = () => {\n if (this.frame) return;\n this.frame = this.win.requestAnimationFrame(() => {\n this.frame = 0;\n this.sync();\n });\n };\n\n /** 安装后立即对齐一次当前焦点。 */\n syncNow = () => this.sync();\n\n private clear = () => {\n if (this.ring.matches(':popover-open')) this.ring.hidePopover();\n this.restoreTarget?.();\n this.restoreTarget = undefined;\n this.target = undefined;\n };\n\n /** 找到 ring 应该挂的父节点:向上跳过 contain: none 的祖先。 */\n private resolveRingParent(element: HTMLElement): HTMLElement {\n let scope = element.parentElement;\n while (scope && this.win.getComputedStyle(scope).contain === 'none')\n scope = scope.parentElement;\n return scope || this.doc.body;\n }\n\n /** 把目标元素的内联样式/标记改回去,仅当值仍是我们写入的那个。 */\n private static restoreInlineStyles(\n element: HTMLElement,\n saved: InlineStyleSnapshot[],\n assignedAnchor: string,\n oldMarker: string | null,\n ) {\n for (const { property, value, priority } of saved) {\n const assigned = property === 'anchor-name' ? assignedAnchor : 'none';\n if (element.style.getPropertyValue(property) !== assigned) continue;\n if (value) element.style.setProperty(property, value, priority);\n else element.style.removeProperty(property);\n }\n if (element.getAttribute(MANAGED_ATTRIBUTE) === 'managed') {\n if (oldMarker === null) element.removeAttribute(MANAGED_ATTRIBUTE);\n else element.setAttribute(MANAGED_ATTRIBUTE, oldMarker);\n }\n }\n\n private sync = () => {\n if (this.disposed) return;\n const next = this.doc.activeElement;\n\n const alreadyShowing =\n next === this.target &&\n next?.matches(':focus-visible') &&\n next.isConnected &&\n this.ring.matches(':popover-open');\n if (alreadyShowing) return;\n\n this.clear();\n if (!isRingTarget(next, this.doc)) return;\n\n const element = next;\n const ringParent = this.resolveRingParent(element);\n const moved = this.ring.parentElement !== ringParent;\n if (moved) ringParent.append(this.ring);\n\n const computed = this.win.getComputedStyle(element);\n const borderRadius = computed.borderRadius;\n const color =\n computed.getPropertyValue('--superad-focus-ring-color').trim() ||\n computed.getPropertyValue('--ant-color-info').trim() ||\n FALLBACK_RING_COLOR;\n const styleChanged =\n this.ring.style.borderRadius !== borderRadius ||\n this.ring.style.getPropertyValue('--superad-focus-ring-color') !== color;\n if (styleChanged) {\n this.ring.style.borderRadius = borderRadius;\n this.ring.style.setProperty('--superad-focus-ring-color', color);\n }\n // 刚移动或刚改样式时锚点还没生效,下一帧再同步\n if (moved || styleChanged) {\n this.schedule();\n return;\n }\n\n const saved: InlineStyleSnapshot[] = ['anchor-name', 'outline'].map((property) => ({\n priority: element.style.getPropertyPriority(property),\n property,\n value: element.style.getPropertyValue(property),\n }));\n const oldMarker = element.getAttribute(MANAGED_ATTRIBUTE);\n const existingAnchor = computed.getPropertyValue('anchor-name').trim();\n const assignedAnchor =\n existingAnchor && existingAnchor !== 'none'\n ? `${existingAnchor}, ${ANCHOR_NAME}`\n : ANCHOR_NAME;\n element.style.setProperty('anchor-name', assignedAnchor, 'important');\n\n this.restoreTarget = () =>\n FocusRingController.restoreInlineStyles(element, saved, assignedAnchor, oldMarker);\n\n const rect = element.getBoundingClientRect();\n const hasUsableBox =\n element.getClientRects().length === 1 &&\n Boolean(rect.width) &&\n Boolean(rect.height) &&\n computed.visibility === 'visible';\n if (!hasUsableBox) {\n this.clear();\n return;\n }\n\n try {\n this.ring.showPopover();\n const bounds = this.ring.getBoundingClientRect();\n const maxEdgeDelta = Math.max(\n ...(['top', 'right', 'bottom', 'left'] as const).map((edge) =>\n Math.abs(bounds[edge] - rect[edge]),\n ),\n );\n // 锚点没对齐(比如目标在 transform 容器里)就放弃,不显示错位的环\n if (maxEdgeDelta > 1) {\n this.clear();\n return;\n }\n element.style.setProperty('outline', 'none', 'important');\n element.setAttribute(MANAGED_ATTRIBUTE, 'managed');\n this.target = element;\n } catch {\n this.clear();\n }\n };\n}\n\nconst installations = new WeakMap<Document, { controller: FocusRingController; users: number }>();\n\n/** 每个 document 安装一次;返回的释放函数对嵌套 Provider 与 Strict Mode 安全。 */\nexport const installGlobalFocusRing = (doc: Document = document): (() => void) => {\n const existing = installations.get(doc);\n if (existing) {\n existing.users += 1;\n let released = false;\n return () => {\n if (released) return;\n released = true;\n existing.users -= 1;\n if (existing.users === 0) {\n existing.controller.dispose();\n installations.delete(doc);\n }\n };\n }\n\n if (!isSupported(doc) || !doc.body) return () => {};\n\n const controller = new FocusRingController(doc);\n installations.set(doc, { controller, users: 0 });\n controller.syncNow();\n // 走一遍上面的引用计数分支,拿到释放函数\n return installGlobalFocusRing(doc);\n};\n"],"mappings":"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"}
@@ -95,6 +95,7 @@ const createTagLayout = (options, placeholder, duration) => (args) => {
95
95
  const record = args.table?.getCellOriginRecord?.(args.col, args.row);
96
96
  const { suffix, width: suffixWidth } = tagDurationSuffix(record, duration);
97
97
  const root = new CustomLayout.Group({
98
+ clip: true,
98
99
  height,
99
100
  pickable: false,
100
101
  width
@@ -1 +1 @@
1
- {"version":3,"file":"chipPainters.mjs","names":[],"sources":["../../../src/Table/canvas/chipPainters.ts"],"sourcesContent":["'use client';\n\nimport { CustomLayout } from '@visactor/vtable';\nimport type { ICustomLayoutFuc } from '@visactor/vtable/es/ts-types/customLayout';\n\nimport {\n formatDuration,\n formatLinkLabel,\n type LinkKind,\n resolveDurationRange,\n resolveOption,\n resolveUser,\n toValueList,\n} from '../core/cellValue';\nimport { toneFor } from '../core/tagTone';\nimport type { TableSelectOption, TableUser } from '../type';\nimport { type CanvasPalette, getCanvasPalette } from './canvasPalette';\nimport {\n ARRAY_PAD_LEFT,\n editHintGutterFor,\n fitLabel,\n LINK_PAD_LEFT,\n measureArrayChipWidth,\n measureLabelWidth,\n measureLinkPillWidth,\n measureOverflowBadgeWidth,\n measureTagWidth,\n overflowBadgeWidth,\n packItems,\n TAG_FONT_SIZE,\n TAG_GAP,\n TAG_PAD_LEFT,\n} from './paintUtils';\nimport { createEmptyCellText } from './textPainters';\n\nexport { EDIT_HINT_GUTTER, editHintGutterFor } from './paintUtils';\n\n/**\n * \"+N\" badge shown when chips do not fit. Shared by every chip layout so the badge\n * keeps one size, colour and baseline.\n */\nconst createOverflowText = (count: number, palette: CanvasPalette, height: number, x: number) =>\n new CustomLayout.Text({\n fontFamily: palette.fontFamily,\n fill: palette.text,\n fontSize: 13,\n pickable: false,\n text: `+${count}`,\n textAlign: 'left',\n textBaseline: 'middle',\n x,\n y: height / 2,\n } as never);\n\n/**\n * 打包用的可用宽度。绘制是从 `palette.paddingX` 起笔的,不减掉左右内边距的话,最后一个\n * chip 会顶到甚至越过单元格右边界 —— 自定义容器没有 clip,会盖到相邻单元格上。\n * 文本 painter 早就是这么算的(`width - paddingX * 2`)。\n */\nconst availableWidth = (width: number, paddingX: number): number =>\n Math.max(width - paddingX * 2, 48);\n\n/** 画布与 DOM 悬停浮层共用的 chip 排布结果。 */\nexport interface ChipBox {\n /** 在**记录原始数组**里的下标 —— 移除时按它 splice,不能用过滤后的位置。 */\n index: number;\n /** 画进 chip 的文字(可能已被截断)。 */\n label: string;\n width: number;\n /** 相对单元格左边界的起点,与 painter 的落笔位置同源。 */\n x: number;\n}\n\n/**\n * 各个 chip 布局共用的排布:打包 → 按可用宽截断 → 从左往右累加 x。\n *\n * 抽成自由函数是因为**画布与 DOM 悬停浮层必须共用同一份计算**:浮层要在画布画出来的 chip\n * 正上方盖一个 ×,两边各算一次就会错位。用到的度量(`measureLabelWidth` / `fitLabel` /\n * `packItems`)都是纯函数、不依赖 canvas 上下文,所以 DOM 这边也能直接调。\n *\n * `Extra` 让调用方带上自己的载荷(标签带 option、人员带 user),排布本身不关心那是什么。\n */\nconst layoutChips = <Extra>(\n items: ({ index: number; label: string } & Extra)[],\n widthOf: (label: string) => number,\n overflowOf: (rest: number) => number,\n { paddingX, width }: { paddingX: number; width: number },\n): { boxes: (ChipBox & Extra)[]; overflow: number; overflowX: number } => {\n const available = availableWidth(width, paddingX);\n const packed = packItems(\n items.map(({ label }) => widthOf(label)),\n available,\n overflowOf,\n );\n const boxes: (ChipBox & Extra)[] = [];\n let x = paddingX;\n\n items.slice(0, packed.visible).forEach((item) => {\n // 截断必须发生在**算宽度之前**:只截文本节点的话矩形仍按完整标签算宽,照样越界。\n const label = fitLabel(item.label, available - overflowBadgeWidth(packed.overflow), widthOf);\n const chipWidth = widthOf(label);\n\n boxes.push({ ...item, label, width: chipWidth, x });\n x += chipWidth + TAG_GAP;\n });\n\n // `x` 已经停在下一枚 chip 该落笔的位置,正好是 +N 徽标的起点。\n return { boxes, overflow: packed.overflow, overflowX: x };\n};\n\n/**\n * 标签 chip 的排布。供悬停浮层在画布画出来的 chip 上盖 × 用。\n *\n * 值的容器类型不同(数组 / 单个字符串)不影响排布 —— `packed.visible` 之外的那些由 +N 徽标\n * 代表,`index` 始终是**原始数组下标**。\n */\n/**\n * 状态 chip 外标注的时长文案与它占的宽度。**画布与悬停浮层必须用同一份**:打包预算要把它\n * 预留掉,两边各算一遍截断点就会漂移。\n */\nexport const tagDurationSuffix = (\n record: unknown,\n duration?: string | { from: string; to: string },\n): { suffix: string; width: number } => {\n const text = formatDuration(resolveDurationRange(record, duration));\n if (text === '') return { suffix: '', width: 0 };\n\n const suffix = ` ${text}`;\n return { suffix, width: TAG_GAP + measureLabelWidth(suffix, TAG_FONT_SIZE) };\n};\n\nexport const layoutTagChips = ({\n options,\n paddingX,\n reserve = 0,\n value,\n width,\n}: {\n options: TableSelectOption[] | undefined;\n paddingX: number;\n /** chip 之后还要放独立文字(「5天」这类时长)时预留的宽度(含间距)。 */\n reserve?: number;\n value: unknown;\n width: number;\n}): {\n boxes: (ChipBox & { option: TableSelectOption })[];\n overflow: number;\n overflowX: number;\n} =>\n layoutChips(\n toValueList(value)\n .map((item) => resolveOption(item, options))\n .map((option, index) => ({ index, label: option.label, option })),\n measureTagWidth,\n (rest) => measureOverflowBadgeWidth(`+${rest}`),\n { paddingX, width: width - reserve },\n );\n\nexport const createTagLayout =\n (\n options: TableSelectOption[] | undefined,\n placeholder?: string,\n duration?: string | { from: string; to: string },\n ): ICustomLayoutFuc =>\n (args) => {\n const palette = getCanvasPalette(args.table);\n const width = args.rect?.width ?? 120;\n const height = args.rect?.height ?? 40;\n // 状态标题后跟的时长(「5天 / 1个月」):`duration` 指名记录里存日期范围的字段,时长现算,\n // 作为 chip **外面**的独立文字画出来 —— 参考图里它就是 chip 后面的标注,不是 chip 的一部分。\n const record = (args.table as any)?.getCellOriginRecord?.(args.col, args.row);\n const { suffix, width: suffixWidth } = tagDurationSuffix(record, duration);\n const root = new CustomLayout.Group({ height, pickable: false, width });\n const { boxes, overflow, overflowX } = layoutTagChips({\n options,\n paddingX: palette.paddingX,\n reserve: suffixWidth + editHintGutterFor(args.table as never, args.col, args.row),\n value: args.value,\n width,\n });\n\n if (boxes.length === 0) {\n if (placeholder) {\n root.add(createEmptyCellText(placeholder, palette, height, args.table));\n }\n\n return { enableCellPadding: false, renderDefault: false, rootContainer: root };\n }\n\n const chipHeight = 24;\n const chipTop = Math.max(0, (height - chipHeight) / 2);\n\n boxes.forEach(({ label, option, width: chipWidth, x }) => {\n const tone = toneFor(option.value, option.color, palette.isDarkMode, palette.checkboxBg);\n // 文字是主题文字色而不是 tone.color:浅底 + 统一近黑字是「状态」参考图的样式,也正好是\n // antd Tag 默认的「浅底 + colorText 字」。tone.color 仍是色相源(hex 吸附、暗色提亮都靠\n // 它),暗色下 chip 底是彩色半透明,白字对比度没有保证,所以暗色继续用 tone 的提亮色。\n const textFill = palette.isDarkMode ? tone.color : palette.text;\n\n root.add(\n new CustomLayout.Rect({\n cornerRadius: 6,\n fill: tone.bg,\n height: chipHeight,\n lineWidth: tone.border ? 1 : 0,\n pickable: false,\n stroke: tone.border,\n width: chipWidth,\n x,\n y: chipTop,\n } as never),\n );\n root.add(\n new CustomLayout.Text({\n fontFamily: palette.fontFamily,\n fill: textFill,\n fontSize: TAG_FONT_SIZE,\n pickable: false,\n text: label,\n textAlign: 'left',\n textBaseline: 'middle',\n x: x + TAG_PAD_LEFT,\n y: height / 2,\n } as never),\n );\n });\n\n // 时长文字画在**最后一枚 chip 的外面**(参考图里「4天」是 chip 后面的标注),\n // 用弱色 —— 它是补充信息,不能与 chip 标题同权重。已通过 `reserve` 参与打包预算。\n // 极窄列 + 长时长时剩余宽度装不下完整标注,按剩余宽度截断 —— 不能顶出单元格。\n let after = overflowX;\n if (suffix !== '') {\n const remaining = Math.max(0, width - palette.paddingX - (overflowX + TAG_GAP));\n const drawnSuffix =\n remaining >= suffixWidth\n ? suffix\n : fitLabel(suffix, remaining, (label) => measureLabelWidth(label, TAG_FONT_SIZE));\n root.add(\n new CustomLayout.Text({\n fontFamily: palette.fontFamily,\n fill: palette.textMuted,\n fontSize: TAG_FONT_SIZE,\n pickable: false,\n text: drawnSuffix,\n textAlign: 'left',\n textBaseline: 'middle',\n x: overflowX + TAG_GAP,\n y: height / 2,\n } as never),\n );\n after += suffixWidth;\n }\n\n if (overflow > 0) {\n root.add(createOverflowText(overflow, palette, height, after));\n }\n\n return { enableCellPadding: false, renderDefault: false, rootContainer: root };\n };\n\n/** 人员 chip 的固定尺寸,画布与 DOM 悬停浮层共用。 */\nexport const USER_CHIP_HEIGHT = 24;\nexport const USER_CHIP_AVATAR = 14;\n/**\n * chip 右侧留白。**不是纯装饰**:悬停时要在这一格里盖一个 14px 的移除按钮。\n *\n * 取值有下界:文字从 `x + 22` 起笔,按钮是「贴右 + margin-inline-end 2px」,所以留白必须\n * ≥ 4 + 2 + 14 = 20 才不压到字上。留 8px 时按钮整个盖在最后一个字上 —— 近黑的 × 叠在近黑的\n * 笔画上等于没画,实测截图里完全看不见。22 是 20 再加 2px 余量。\n */\nexport const USER_CHIP_TRAILING = 22;\n\nexport interface UserChipBox extends ChipBox {\n user: TableUser;\n}\n\n/**\n * 人员 chip 的排布。\n *\n * 抽成自由函数是因为**画布与 DOM 悬停浮层必须共用同一份计算**:浮层要在画布画出来的 chip\n * 正上方盖一个 ×,两边各算一次就会错位。用到的度量(`measureLabelWidth` / `fitLabel` /\n * `packItems`)都是纯函数、不依赖 canvas 上下文,所以 DOM 这边也能直接调。\n */\nexport const layoutUserChips = ({\n paddingX,\n users,\n width,\n}: {\n paddingX: number;\n users: { index: number; user: TableUser }[];\n width: number;\n}): { boxes: UserChipBox[]; overflow: number; overflowX: number } =>\n layoutChips(\n users.map(({ index, user }) => ({ index, label: user.name, user })),\n // Packing and drawing must share one expression: the avatar's width is what makes them\n // differ, and a mismatch would shift the +N overflow count.\n (label) => USER_CHIP_AVATAR + 4 + measureLabelWidth(label, 13) + USER_CHIP_TRAILING,\n () => 24,\n { paddingX, width },\n );\n\n/** 值的每一项解析成人员并保留**原始下标**;没有可用名字的(`{}` / `{ id }` / `{ name: 42 }`)\n * 一律丢掉 —— `isEmptyCellValue({})` 判它是空值,两条路径口径必须一致。 */\nexport const resolveUserEntries = (value: unknown): { index: number; user: TableUser }[] =>\n toValueList(value)\n .map(resolveUser)\n .map((user, index) => ({ index, user }))\n .filter(({ user }) => user.name !== '');\n\nexport const createUserLayout =\n (placeholder?: string): ICustomLayoutFuc =>\n (args) => {\n const palette = getCanvasPalette(args.table);\n const width = args.rect?.width ?? 140;\n const height = args.rect?.height ?? 40;\n const entries = resolveUserEntries(args.value);\n const root = new CustomLayout.Group({ height, pickable: false, width });\n\n if (entries.length === 0) {\n if (placeholder) {\n root.add(createEmptyCellText(placeholder, palette, height, args.table));\n }\n\n return { enableCellPadding: false, renderDefault: false, rootContainer: root };\n }\n\n const chipTop = Math.max(0, (height - USER_CHIP_HEIGHT) / 2);\n const { boxes, overflow, overflowX } = layoutUserChips({\n paddingX: palette.paddingX,\n users: entries,\n width: width - editHintGutterFor(args.table as never, args.col, args.row),\n });\n\n boxes.forEach(({ label, user, width: chipWidth, x }) => {\n const tone = toneFor(user.name, undefined, palette.isDarkMode, palette.checkboxBg);\n const initials = user.name.trim().slice(0, 1) || '?';\n\n root.add(\n new CustomLayout.Rect({\n cornerRadius: 4,\n fill: palette.pillBg,\n height: USER_CHIP_HEIGHT,\n pickable: false,\n width: chipWidth,\n x,\n y: chipTop,\n } as never),\n );\n if (user.avatar) {\n root.add(\n new CustomLayout.Image({\n cornerRadius: 999,\n height: USER_CHIP_AVATAR,\n pickable: false,\n shape: 'square',\n src: user.avatar,\n width: USER_CHIP_AVATAR,\n x: x + 4,\n y: chipTop + (USER_CHIP_HEIGHT - USER_CHIP_AVATAR) / 2,\n } as never),\n );\n } else {\n root.add(\n new CustomLayout.Rect({\n // 圆形:人员头像一律是圆的(参考图里单元格 chip 与选择面板同形)。\n cornerRadius: 999,\n fill: tone.bg,\n height: USER_CHIP_AVATAR,\n pickable: false,\n width: USER_CHIP_AVATAR,\n x: x + 4,\n y: chipTop + (USER_CHIP_HEIGHT - USER_CHIP_AVATAR) / 2,\n } as never),\n );\n root.add(\n new CustomLayout.Text({\n fontFamily: palette.fontFamily,\n fill: tone.color,\n fontSize: 9,\n pickable: false,\n text: initials,\n textAlign: 'center',\n textBaseline: 'middle',\n x: x + 4 + USER_CHIP_AVATAR / 2,\n y: chipTop + (USER_CHIP_HEIGHT - USER_CHIP_AVATAR) / 2 + USER_CHIP_AVATAR / 2,\n } as never),\n );\n }\n root.add(\n new CustomLayout.Text({\n fontFamily: palette.fontFamily,\n fill: palette.text,\n fontSize: 13,\n pickable: false,\n text: label,\n textAlign: 'left',\n textBaseline: 'middle',\n x: x + 4 + USER_CHIP_AVATAR + 4,\n y: height / 2,\n } as never),\n );\n });\n\n if (overflow > 0) {\n root.add(createOverflowText(overflow, palette, height, overflowX));\n }\n\n return { enableCellPadding: false, renderDefault: false, rootContainer: root };\n };\n\n/** 值 → 文本项,保留**原始下标**;空串不画(与 painter 原先的 `filter(Boolean)` 同口径)。\n * 下标必须是原始的:记录是 `['', '核心']` 时画面上只有一枚 chip,点它的 × 要删掉「核心」。 */\nconst resolveTextEntries = (value: unknown): { index: number; label: string }[] =>\n toValueList(value)\n .map((item) => String(item))\n .map((label, index) => ({ index, label }))\n .filter(({ label }) => label !== '');\n\n/** 链接 pill 的排布。文案与画布同源:`kind === 'link'` 时要一起剥掉 scheme。 */\nexport const layoutLinkChips = ({\n kind,\n paddingX,\n value,\n width,\n}: {\n kind: LinkKind;\n paddingX: number;\n value: unknown;\n width: number;\n}): { boxes: ChipBox[]; overflow: number; overflowX: number } =>\n layoutChips(\n resolveTextEntries(value).map(({ index, label }) => ({\n index,\n label: kind === 'link' ? formatLinkLabel(label) : label,\n })),\n measureLinkPillWidth,\n () => 24,\n { paddingX, width },\n );\n\nexport const createLinkLayout =\n (kind: LinkKind, placeholder?: string): ICustomLayoutFuc =>\n (args) => {\n const palette = getCanvasPalette(args.table);\n const width = args.rect?.width ?? 140;\n const height = args.rect?.height ?? 40;\n const root = new CustomLayout.Group({ height, pickable: false, width });\n const { boxes, overflow, overflowX } = layoutLinkChips({\n kind,\n paddingX: palette.paddingX,\n value: args.value,\n width: width - editHintGutterFor(args.table as never, args.col, args.row),\n });\n\n if (boxes.length === 0) {\n if (placeholder) {\n root.add(createEmptyCellText(placeholder, palette, height, args.table));\n }\n\n return { enableCellPadding: false, renderDefault: false, rootContainer: root };\n }\n\n const pillHeight = 24;\n const pillTop = Math.max(0, (height - pillHeight) / 2);\n\n boxes.forEach(({ label, width: pillWidth, x }) => {\n root.add(\n new CustomLayout.Rect({\n cornerRadius: 999,\n fill: palette.pillBg,\n height: pillHeight,\n lineWidth: 1,\n pickable: false,\n stroke: palette.overlayBorder,\n width: pillWidth,\n x,\n y: pillTop,\n } as never),\n );\n root.add(\n new CustomLayout.Text({\n fontFamily: palette.fontFamily,\n fill: palette.text,\n fontSize: 13,\n pickable: false,\n text: label,\n textAlign: 'left',\n textBaseline: 'middle',\n x: x + LINK_PAD_LEFT,\n y: height / 2,\n } as never),\n );\n });\n\n if (overflow > 0) {\n root.add(createOverflowText(overflow, palette, height, overflowX));\n }\n\n return { enableCellPadding: false, renderDefault: false, rootContainer: root };\n };\n\n/** 数组 chip 的排布。内边距与标签 / 链接同为 CHIP_PAD —— 右内边距要整颗装下悬停的 ×。 */\nexport const layoutArrayChips = ({\n paddingX,\n value,\n width,\n}: {\n paddingX: number;\n value: unknown;\n width: number;\n}): { boxes: ChipBox[]; overflow: number; overflowX: number } =>\n layoutChips(resolveTextEntries(value), measureArrayChipWidth, () => 24, { paddingX, width });\n\nexport const createArrayLayout =\n (placeholder?: string): ICustomLayoutFuc =>\n (args) => {\n const palette = getCanvasPalette(args.table);\n const width = args.rect?.width ?? 140;\n const height = args.rect?.height ?? 40;\n const root = new CustomLayout.Group({ height, pickable: false, width });\n const { boxes, overflow, overflowX } = layoutArrayChips({\n paddingX: palette.paddingX,\n value: args.value,\n width: width - editHintGutterFor(args.table as never, args.col, args.row),\n });\n\n if (boxes.length === 0) {\n if (placeholder) {\n root.add(createEmptyCellText(placeholder, palette, height, args.table));\n }\n\n return { enableCellPadding: false, renderDefault: false, rootContainer: root };\n }\n\n const chipHeight = 24;\n const chipTop = Math.max(0, (height - chipHeight) / 2);\n\n boxes.forEach(({ label, width: chipWidth, x }) => {\n root.add(\n new CustomLayout.Rect({\n cornerRadius: 4,\n fill: palette.pillBg,\n height: chipHeight,\n pickable: false,\n width: chipWidth,\n x,\n y: chipTop,\n } as never),\n );\n root.add(\n new CustomLayout.Text({\n fontFamily: palette.fontFamily,\n fill: palette.text,\n fontSize: 13,\n pickable: false,\n text: label,\n textAlign: 'left',\n textBaseline: 'middle',\n x: x + ARRAY_PAD_LEFT,\n y: height / 2,\n } as never),\n );\n });\n\n if (overflow > 0) {\n root.add(createOverflowText(overflow, palette, height, overflowX));\n }\n\n return { enableCellPadding: false, renderDefault: false, rootContainer: root };\n };\n"],"mappings":"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"}
1
+ {"version":3,"file":"chipPainters.mjs","names":[],"sources":["../../../src/Table/canvas/chipPainters.ts"],"sourcesContent":["'use client';\n\nimport { CustomLayout } from '@visactor/vtable';\nimport type { ICustomLayoutFuc } from '@visactor/vtable/es/ts-types/customLayout';\n\nimport {\n formatDuration,\n formatLinkLabel,\n type LinkKind,\n resolveDurationRange,\n resolveOption,\n resolveUser,\n toValueList,\n} from '../core/cellValue';\nimport { toneFor } from '../core/tagTone';\nimport type { TableSelectOption, TableUser } from '../type';\nimport { type CanvasPalette, getCanvasPalette } from './canvasPalette';\nimport {\n ARRAY_PAD_LEFT,\n editHintGutterFor,\n fitLabel,\n LINK_PAD_LEFT,\n measureArrayChipWidth,\n measureLabelWidth,\n measureLinkPillWidth,\n measureOverflowBadgeWidth,\n measureTagWidth,\n overflowBadgeWidth,\n packItems,\n TAG_FONT_SIZE,\n TAG_GAP,\n TAG_PAD_LEFT,\n} from './paintUtils';\nimport { createEmptyCellText } from './textPainters';\n\nexport { EDIT_HINT_GUTTER, editHintGutterFor } from './paintUtils';\n\n/**\n * \"+N\" badge shown when chips do not fit. Shared by every chip layout so the badge\n * keeps one size, colour and baseline.\n */\nconst createOverflowText = (count: number, palette: CanvasPalette, height: number, x: number) =>\n new CustomLayout.Text({\n fontFamily: palette.fontFamily,\n fill: palette.text,\n fontSize: 13,\n pickable: false,\n text: `+${count}`,\n textAlign: 'left',\n textBaseline: 'middle',\n x,\n y: height / 2,\n } as never);\n\n/**\n * 打包用的可用宽度。绘制是从 `palette.paddingX` 起笔的,不减掉左右内边距的话,最后一个\n * chip 会顶到甚至越过单元格右边界 —— 自定义容器没有 clip,会盖到相邻单元格上。\n * 文本 painter 早就是这么算的(`width - paddingX * 2`)。\n */\nconst availableWidth = (width: number, paddingX: number): number =>\n Math.max(width - paddingX * 2, 48);\n\n/** 画布与 DOM 悬停浮层共用的 chip 排布结果。 */\nexport interface ChipBox {\n /** 在**记录原始数组**里的下标 —— 移除时按它 splice,不能用过滤后的位置。 */\n index: number;\n /** 画进 chip 的文字(可能已被截断)。 */\n label: string;\n width: number;\n /** 相对单元格左边界的起点,与 painter 的落笔位置同源。 */\n x: number;\n}\n\n/**\n * 各个 chip 布局共用的排布:打包 → 按可用宽截断 → 从左往右累加 x。\n *\n * 抽成自由函数是因为**画布与 DOM 悬停浮层必须共用同一份计算**:浮层要在画布画出来的 chip\n * 正上方盖一个 ×,两边各算一次就会错位。用到的度量(`measureLabelWidth` / `fitLabel` /\n * `packItems`)都是纯函数、不依赖 canvas 上下文,所以 DOM 这边也能直接调。\n *\n * `Extra` 让调用方带上自己的载荷(标签带 option、人员带 user),排布本身不关心那是什么。\n */\nconst layoutChips = <Extra>(\n items: ({ index: number; label: string } & Extra)[],\n widthOf: (label: string) => number,\n overflowOf: (rest: number) => number,\n { paddingX, width }: { paddingX: number; width: number },\n): { boxes: (ChipBox & Extra)[]; overflow: number; overflowX: number } => {\n const available = availableWidth(width, paddingX);\n const packed = packItems(\n items.map(({ label }) => widthOf(label)),\n available,\n overflowOf,\n );\n const boxes: (ChipBox & Extra)[] = [];\n let x = paddingX;\n\n items.slice(0, packed.visible).forEach((item) => {\n // 截断必须发生在**算宽度之前**:只截文本节点的话矩形仍按完整标签算宽,照样越界。\n const label = fitLabel(item.label, available - overflowBadgeWidth(packed.overflow), widthOf);\n const chipWidth = widthOf(label);\n\n boxes.push({ ...item, label, width: chipWidth, x });\n x += chipWidth + TAG_GAP;\n });\n\n // `x` 已经停在下一枚 chip 该落笔的位置,正好是 +N 徽标的起点。\n return { boxes, overflow: packed.overflow, overflowX: x };\n};\n\n/**\n * 标签 chip 的排布。供悬停浮层在画布画出来的 chip 上盖 × 用。\n *\n * 值的容器类型不同(数组 / 单个字符串)不影响排布 —— `packed.visible` 之外的那些由 +N 徽标\n * 代表,`index` 始终是**原始数组下标**。\n */\n/**\n * 状态 chip 外标注的时长文案与它占的宽度。**画布与悬停浮层必须用同一份**:打包预算要把它\n * 预留掉,两边各算一遍截断点就会漂移。\n */\nexport const tagDurationSuffix = (\n record: unknown,\n duration?: string | { from: string; to: string },\n): { suffix: string; width: number } => {\n const text = formatDuration(resolveDurationRange(record, duration));\n if (text === '') return { suffix: '', width: 0 };\n\n const suffix = ` ${text}`;\n return { suffix, width: TAG_GAP + measureLabelWidth(suffix, TAG_FONT_SIZE) };\n};\n\nexport const layoutTagChips = ({\n options,\n paddingX,\n reserve = 0,\n value,\n width,\n}: {\n options: TableSelectOption[] | undefined;\n paddingX: number;\n /** chip 之后还要放独立文字(「5天」这类时长)时预留的宽度(含间距)。 */\n reserve?: number;\n value: unknown;\n width: number;\n}): {\n boxes: (ChipBox & { option: TableSelectOption })[];\n overflow: number;\n overflowX: number;\n} =>\n layoutChips(\n toValueList(value)\n .map((item) => resolveOption(item, options))\n .map((option, index) => ({ index, label: option.label, option })),\n measureTagWidth,\n (rest) => measureOverflowBadgeWidth(`+${rest}`),\n { paddingX, width: width - reserve },\n );\n\nexport const createTagLayout =\n (\n options: TableSelectOption[] | undefined,\n placeholder?: string,\n duration?: string | { from: string; to: string },\n ): ICustomLayoutFuc =>\n (args) => {\n const palette = getCanvasPalette(args.table);\n const width = args.rect?.width ?? 120;\n const height = args.rect?.height ?? 40;\n // 状态标题后跟的时长(「5天 / 1个月」):`duration` 指名记录里存日期范围的字段,时长现算,\n // 作为 chip **外面**的独立文字画出来 —— 参考图里它就是 chip 后面的标注,不是 chip 的一部分。\n const record = (args.table as any)?.getCellOriginRecord?.(args.col, args.row);\n const { suffix, width: suffixWidth } = tagDurationSuffix(record, duration);\n // clip:时长标注估宽偏短时不能把 AABBBounds 撑过单元格,否则 auto 列宽会按越界值回写、越编越宽。\n const root = new CustomLayout.Group({ clip: true, height, pickable: false, width });\n const { boxes, overflow, overflowX } = layoutTagChips({\n options,\n paddingX: palette.paddingX,\n reserve: suffixWidth + editHintGutterFor(args.table as never, args.col, args.row),\n value: args.value,\n width,\n });\n\n if (boxes.length === 0) {\n if (placeholder) {\n root.add(createEmptyCellText(placeholder, palette, height, args.table));\n }\n\n return { enableCellPadding: false, renderDefault: false, rootContainer: root };\n }\n\n const chipHeight = 24;\n const chipTop = Math.max(0, (height - chipHeight) / 2);\n\n boxes.forEach(({ label, option, width: chipWidth, x }) => {\n const tone = toneFor(option.value, option.color, palette.isDarkMode, palette.checkboxBg);\n // 文字是主题文字色而不是 tone.color:浅底 + 统一近黑字是「状态」参考图的样式,也正好是\n // antd Tag 默认的「浅底 + colorText 字」。tone.color 仍是色相源(hex 吸附、暗色提亮都靠\n // 它),暗色下 chip 底是彩色半透明,白字对比度没有保证,所以暗色继续用 tone 的提亮色。\n const textFill = palette.isDarkMode ? tone.color : palette.text;\n\n root.add(\n new CustomLayout.Rect({\n cornerRadius: 6,\n fill: tone.bg,\n height: chipHeight,\n lineWidth: tone.border ? 1 : 0,\n pickable: false,\n stroke: tone.border,\n width: chipWidth,\n x,\n y: chipTop,\n } as never),\n );\n root.add(\n new CustomLayout.Text({\n fontFamily: palette.fontFamily,\n fill: textFill,\n fontSize: TAG_FONT_SIZE,\n pickable: false,\n text: label,\n textAlign: 'left',\n textBaseline: 'middle',\n x: x + TAG_PAD_LEFT,\n y: height / 2,\n } as never),\n );\n });\n\n // 时长文字画在**最后一枚 chip 的外面**(参考图里「4天」是 chip 后面的标注),\n // 用弱色 —— 它是补充信息,不能与 chip 标题同权重。已通过 `reserve` 参与打包预算。\n // 极窄列 + 长时长时剩余宽度装不下完整标注,按剩余宽度截断 —— 不能顶出单元格。\n let after = overflowX;\n if (suffix !== '') {\n const remaining = Math.max(0, width - palette.paddingX - (overflowX + TAG_GAP));\n const drawnSuffix =\n remaining >= suffixWidth\n ? suffix\n : fitLabel(suffix, remaining, (label) => measureLabelWidth(label, TAG_FONT_SIZE));\n root.add(\n new CustomLayout.Text({\n fontFamily: palette.fontFamily,\n fill: palette.textMuted,\n fontSize: TAG_FONT_SIZE,\n pickable: false,\n text: drawnSuffix,\n textAlign: 'left',\n textBaseline: 'middle',\n x: overflowX + TAG_GAP,\n y: height / 2,\n } as never),\n );\n after += suffixWidth;\n }\n\n if (overflow > 0) {\n root.add(createOverflowText(overflow, palette, height, after));\n }\n\n return { enableCellPadding: false, renderDefault: false, rootContainer: root };\n };\n\n/** 人员 chip 的固定尺寸,画布与 DOM 悬停浮层共用。 */\nexport const USER_CHIP_HEIGHT = 24;\nexport const USER_CHIP_AVATAR = 14;\n/**\n * chip 右侧留白。**不是纯装饰**:悬停时要在这一格里盖一个 14px 的移除按钮。\n *\n * 取值有下界:文字从 `x + 22` 起笔,按钮是「贴右 + margin-inline-end 2px」,所以留白必须\n * ≥ 4 + 2 + 14 = 20 才不压到字上。留 8px 时按钮整个盖在最后一个字上 —— 近黑的 × 叠在近黑的\n * 笔画上等于没画,实测截图里完全看不见。22 是 20 再加 2px 余量。\n */\nexport const USER_CHIP_TRAILING = 22;\n\nexport interface UserChipBox extends ChipBox {\n user: TableUser;\n}\n\n/**\n * 人员 chip 的排布。\n *\n * 抽成自由函数是因为**画布与 DOM 悬停浮层必须共用同一份计算**:浮层要在画布画出来的 chip\n * 正上方盖一个 ×,两边各算一次就会错位。用到的度量(`measureLabelWidth` / `fitLabel` /\n * `packItems`)都是纯函数、不依赖 canvas 上下文,所以 DOM 这边也能直接调。\n */\nexport const layoutUserChips = ({\n paddingX,\n users,\n width,\n}: {\n paddingX: number;\n users: { index: number; user: TableUser }[];\n width: number;\n}): { boxes: UserChipBox[]; overflow: number; overflowX: number } =>\n layoutChips(\n users.map(({ index, user }) => ({ index, label: user.name, user })),\n // Packing and drawing must share one expression: the avatar's width is what makes them\n // differ, and a mismatch would shift the +N overflow count.\n (label) => USER_CHIP_AVATAR + 4 + measureLabelWidth(label, 13) + USER_CHIP_TRAILING,\n () => 24,\n { paddingX, width },\n );\n\n/** 值的每一项解析成人员并保留**原始下标**;没有可用名字的(`{}` / `{ id }` / `{ name: 42 }`)\n * 一律丢掉 —— `isEmptyCellValue({})` 判它是空值,两条路径口径必须一致。 */\nexport const resolveUserEntries = (value: unknown): { index: number; user: TableUser }[] =>\n toValueList(value)\n .map(resolveUser)\n .map((user, index) => ({ index, user }))\n .filter(({ user }) => user.name !== '');\n\nexport const createUserLayout =\n (placeholder?: string): ICustomLayoutFuc =>\n (args) => {\n const palette = getCanvasPalette(args.table);\n const width = args.rect?.width ?? 140;\n const height = args.rect?.height ?? 40;\n const entries = resolveUserEntries(args.value);\n const root = new CustomLayout.Group({ height, pickable: false, width });\n\n if (entries.length === 0) {\n if (placeholder) {\n root.add(createEmptyCellText(placeholder, palette, height, args.table));\n }\n\n return { enableCellPadding: false, renderDefault: false, rootContainer: root };\n }\n\n const chipTop = Math.max(0, (height - USER_CHIP_HEIGHT) / 2);\n const { boxes, overflow, overflowX } = layoutUserChips({\n paddingX: palette.paddingX,\n users: entries,\n width: width - editHintGutterFor(args.table as never, args.col, args.row),\n });\n\n boxes.forEach(({ label, user, width: chipWidth, x }) => {\n const tone = toneFor(user.name, undefined, palette.isDarkMode, palette.checkboxBg);\n const initials = user.name.trim().slice(0, 1) || '?';\n\n root.add(\n new CustomLayout.Rect({\n cornerRadius: 4,\n fill: palette.pillBg,\n height: USER_CHIP_HEIGHT,\n pickable: false,\n width: chipWidth,\n x,\n y: chipTop,\n } as never),\n );\n if (user.avatar) {\n root.add(\n new CustomLayout.Image({\n cornerRadius: 999,\n height: USER_CHIP_AVATAR,\n pickable: false,\n shape: 'square',\n src: user.avatar,\n width: USER_CHIP_AVATAR,\n x: x + 4,\n y: chipTop + (USER_CHIP_HEIGHT - USER_CHIP_AVATAR) / 2,\n } as never),\n );\n } else {\n root.add(\n new CustomLayout.Rect({\n // 圆形:人员头像一律是圆的(参考图里单元格 chip 与选择面板同形)。\n cornerRadius: 999,\n fill: tone.bg,\n height: USER_CHIP_AVATAR,\n pickable: false,\n width: USER_CHIP_AVATAR,\n x: x + 4,\n y: chipTop + (USER_CHIP_HEIGHT - USER_CHIP_AVATAR) / 2,\n } as never),\n );\n root.add(\n new CustomLayout.Text({\n fontFamily: palette.fontFamily,\n fill: tone.color,\n fontSize: 9,\n pickable: false,\n text: initials,\n textAlign: 'center',\n textBaseline: 'middle',\n x: x + 4 + USER_CHIP_AVATAR / 2,\n y: chipTop + (USER_CHIP_HEIGHT - USER_CHIP_AVATAR) / 2 + USER_CHIP_AVATAR / 2,\n } as never),\n );\n }\n root.add(\n new CustomLayout.Text({\n fontFamily: palette.fontFamily,\n fill: palette.text,\n fontSize: 13,\n pickable: false,\n text: label,\n textAlign: 'left',\n textBaseline: 'middle',\n x: x + 4 + USER_CHIP_AVATAR + 4,\n y: height / 2,\n } as never),\n );\n });\n\n if (overflow > 0) {\n root.add(createOverflowText(overflow, palette, height, overflowX));\n }\n\n return { enableCellPadding: false, renderDefault: false, rootContainer: root };\n };\n\n/** 值 → 文本项,保留**原始下标**;空串不画(与 painter 原先的 `filter(Boolean)` 同口径)。\n * 下标必须是原始的:记录是 `['', '核心']` 时画面上只有一枚 chip,点它的 × 要删掉「核心」。 */\nconst resolveTextEntries = (value: unknown): { index: number; label: string }[] =>\n toValueList(value)\n .map((item) => String(item))\n .map((label, index) => ({ index, label }))\n .filter(({ label }) => label !== '');\n\n/** 链接 pill 的排布。文案与画布同源:`kind === 'link'` 时要一起剥掉 scheme。 */\nexport const layoutLinkChips = ({\n kind,\n paddingX,\n value,\n width,\n}: {\n kind: LinkKind;\n paddingX: number;\n value: unknown;\n width: number;\n}): { boxes: ChipBox[]; overflow: number; overflowX: number } =>\n layoutChips(\n resolveTextEntries(value).map(({ index, label }) => ({\n index,\n label: kind === 'link' ? formatLinkLabel(label) : label,\n })),\n measureLinkPillWidth,\n () => 24,\n { paddingX, width },\n );\n\nexport const createLinkLayout =\n (kind: LinkKind, placeholder?: string): ICustomLayoutFuc =>\n (args) => {\n const palette = getCanvasPalette(args.table);\n const width = args.rect?.width ?? 140;\n const height = args.rect?.height ?? 40;\n const root = new CustomLayout.Group({ height, pickable: false, width });\n const { boxes, overflow, overflowX } = layoutLinkChips({\n kind,\n paddingX: palette.paddingX,\n value: args.value,\n width: width - editHintGutterFor(args.table as never, args.col, args.row),\n });\n\n if (boxes.length === 0) {\n if (placeholder) {\n root.add(createEmptyCellText(placeholder, palette, height, args.table));\n }\n\n return { enableCellPadding: false, renderDefault: false, rootContainer: root };\n }\n\n const pillHeight = 24;\n const pillTop = Math.max(0, (height - pillHeight) / 2);\n\n boxes.forEach(({ label, width: pillWidth, x }) => {\n root.add(\n new CustomLayout.Rect({\n cornerRadius: 999,\n fill: palette.pillBg,\n height: pillHeight,\n lineWidth: 1,\n pickable: false,\n stroke: palette.overlayBorder,\n width: pillWidth,\n x,\n y: pillTop,\n } as never),\n );\n root.add(\n new CustomLayout.Text({\n fontFamily: palette.fontFamily,\n fill: palette.text,\n fontSize: 13,\n pickable: false,\n text: label,\n textAlign: 'left',\n textBaseline: 'middle',\n x: x + LINK_PAD_LEFT,\n y: height / 2,\n } as never),\n );\n });\n\n if (overflow > 0) {\n root.add(createOverflowText(overflow, palette, height, overflowX));\n }\n\n return { enableCellPadding: false, renderDefault: false, rootContainer: root };\n };\n\n/** 数组 chip 的排布。内边距与标签 / 链接同为 CHIP_PAD —— 右内边距要整颗装下悬停的 ×。 */\nexport const layoutArrayChips = ({\n paddingX,\n value,\n width,\n}: {\n paddingX: number;\n value: unknown;\n width: number;\n}): { boxes: ChipBox[]; overflow: number; overflowX: number } =>\n layoutChips(resolveTextEntries(value), measureArrayChipWidth, () => 24, { paddingX, width });\n\nexport const createArrayLayout =\n (placeholder?: string): ICustomLayoutFuc =>\n (args) => {\n const palette = getCanvasPalette(args.table);\n const width = args.rect?.width ?? 140;\n const height = args.rect?.height ?? 40;\n const root = new CustomLayout.Group({ height, pickable: false, width });\n const { boxes, overflow, overflowX } = layoutArrayChips({\n paddingX: palette.paddingX,\n value: args.value,\n width: width - editHintGutterFor(args.table as never, args.col, args.row),\n });\n\n if (boxes.length === 0) {\n if (placeholder) {\n root.add(createEmptyCellText(placeholder, palette, height, args.table));\n }\n\n return { enableCellPadding: false, renderDefault: false, rootContainer: root };\n }\n\n const chipHeight = 24;\n const chipTop = Math.max(0, (height - chipHeight) / 2);\n\n boxes.forEach(({ label, width: chipWidth, x }) => {\n root.add(\n new CustomLayout.Rect({\n cornerRadius: 4,\n fill: palette.pillBg,\n height: chipHeight,\n pickable: false,\n width: chipWidth,\n x,\n y: chipTop,\n } as never),\n );\n root.add(\n new CustomLayout.Text({\n fontFamily: palette.fontFamily,\n fill: palette.text,\n fontSize: 13,\n pickable: false,\n text: label,\n textAlign: 'left',\n textBaseline: 'middle',\n x: x + ARRAY_PAD_LEFT,\n y: height / 2,\n } as never),\n );\n });\n\n if (overflow > 0) {\n root.add(createOverflowText(overflow, palette, height, overflowX));\n }\n\n return { enableCellPadding: false, renderDefault: false, rootContainer: root };\n };\n"],"mappings":"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"}
@@ -5,7 +5,7 @@ type TableToken = Pick<Theme, 'colorBgContainer' | 'colorBorderSecondary' | 'col
5
5
  isDarkMode?: boolean;
6
6
  };
7
7
  interface CreateTableThemeOptions {
8
- /** Show vertical borders between columns. The row index column never gets one. */
8
+ /** Show vertical borders between columns. First / last columns omit their left and right edges. */
9
9
  showVerticalBorder?: boolean;
10
10
  }
11
11
  declare const createTableTheme: (token: TableToken, { showVerticalBorder }?: CreateTableThemeOptions) => TableTheme;
@@ -92,27 +92,53 @@ const resolveOnAccent = (accent) => {
92
92
  return contrastRatio(rgb, ON_ACCENT_DARK_RGB) >= contrastRatio(rgb, ON_ACCENT_LIGHT_RGB) ? ON_ACCENT_DARK : ON_ACCENT_LIGHT;
93
93
  };
94
94
  /** SuperAd rows use a top hairline so separators stay visible; vertical borders are opt-in. */
95
- const hairline = [
96
- 1,
97
- 1,
98
- 0,
99
- 0
100
- ];
101
- /** Default SuperAd table: horizontal top row separator only. */
102
95
  const hairlineNoVertical = [
103
96
  1,
104
97
  0,
105
98
  0,
106
99
  0
107
100
  ];
108
- const createTableTheme = (token, { showVerticalBorder = false } = {}) => {
109
- const borderLineWidth = showVerticalBorder ? hairline : hairlineNoVertical;
110
- const headerBorderLineWidth = showVerticalBorder ? [
101
+ /**
102
+ * `showVerticalBorder` 打开时的列竖线:[top, right, bottom, left]。
103
+ *
104
+ * 第一列(含序号列)左右都不画,因此序号列右侧那条缝也不出现。
105
+ * 最后一列不画右边(表外沿);它的左边用来画出与前一列的内缝。
106
+ * 其余列左右都开。
107
+ *
108
+ * 竖线必须落在单元格**左边**。VTable 默认 `cellBorderClipDirection` 是 `top-left`,
109
+ * 1px 右边框会画进下一格半像素,被下一格底色盖住 —— 只开右边时中间列看起来像没竖线。
110
+ * 不改 clip 方向:改成 `bottom-right` 会把现在靠上边画的行间发丝线盖掉。
111
+ */
112
+ const resolveVerticalBorderLineWidth = (cell, top, showVerticalBorder) => {
113
+ if (!showVerticalBorder) return [
114
+ top,
111
115
  0,
112
- 1,
113
116
  0,
114
117
  0
115
- ] : [
118
+ ];
119
+ const colCount = Math.max(1, cell.table?.colCount ?? 1);
120
+ const last = colCount - 1;
121
+ const range = cell.table?.getCellRange?.(cell.col, cell.row ?? 0);
122
+ const start = range?.start?.col ?? cell.col;
123
+ const end = range?.end?.col ?? cell.col;
124
+ const isFirst = start <= 0;
125
+ const isLast = end >= last;
126
+ if (colCount <= 1 || isFirst) return [
127
+ top,
128
+ 0,
129
+ 0,
130
+ 0
131
+ ];
132
+ return [
133
+ top,
134
+ isLast ? 0 : 1,
135
+ 0,
136
+ start > 1 ? 1 : 0
137
+ ];
138
+ };
139
+ const createTableTheme = (token, { showVerticalBorder = false } = {}) => {
140
+ const borderLineWidth = showVerticalBorder ? (args) => resolveVerticalBorderLineWidth(args, 1, true) : hairlineNoVertical;
141
+ const headerBorderLineWidth = showVerticalBorder ? (args) => resolveVerticalBorderLineWidth(args, 0, true) : [
116
142
  0,
117
143
  0,
118
144
  0,
@@ -271,6 +297,6 @@ const createTableTheme = (token, { showVerticalBorder = false } = {}) => {
271
297
  };
272
298
  };
273
299
  //#endregion
274
- export { TABLE_CHROME, createTableTheme, isOpaqueColor, resolveGroupFill, resolveOnAccent, resolveTableCanvasPalette };
300
+ export { TABLE_CHROME, createTableTheme, isOpaqueColor, resolveGroupFill, resolveOnAccent, resolveTableCanvasPalette, resolveVerticalBorderLineWidth };
275
301
 
276
302
  //# sourceMappingURL=theme.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"theme.mjs","names":[],"sources":["../../../src/Table/canvas/theme.ts"],"sourcesContent":["import type { Theme } from 'antd-style';\nimport { parseToRgb, rgba } from 'polished';\n\nimport { contrastRatio, parseHex, type Rgb } from '../core/tagTone';\nimport type { TableTheme } from '../type';\n\nexport type TableToken = Pick<\n Theme,\n | 'colorBgContainer'\n | 'colorBorderSecondary'\n | 'colorFillQuaternary'\n | 'colorFillSecondary'\n | 'colorFillTertiary'\n | 'colorInfo'\n | 'colorPrimary'\n | 'colorSplit'\n | 'colorSuccess'\n | 'colorText'\n | 'colorTextQuaternary'\n | 'colorTextSecondary'\n | 'colorTextTertiary'\n | 'fontFamily'\n> & { isDarkMode?: boolean };\n/**\n * SuperAd Table visual language, aligned with the Figma Community kit.\n * White header, light bottom hairlines, 44px rows, neutral group rows and a\n * restrained brand-blue accent. Values are fallbacks; createTableTheme still\n * prefers Ant Design / antd-style tokens where they exist.\n */\nexport const TABLE_CHROME = {\n accent: '#1F5EFF',\n // 与 vrender 内置的默认一致:拿不到 antd token 时行为不变(见 DEFAULT_TEXT_FONT_FAMILY)。\n fontFamily:\n \"PingFang SC,Helvetica Neue,Microsoft Yahei,system-ui,'-apple-system',segoe ui,Roboto,Helvetica,Arial,sans-serif\",\n\n border: '#F4F4F5',\n group: '#FAFAFA',\n groupDark: '#161616',\n hover: '#FAFAFA',\n rowHover: '#F5F5F5',\n overlayBorder: '#E4E4E7',\n paddingX: 8,\n pillBg: '#F4F4F5',\n starOff: '#F4F4F5',\n starOn: '#71717A',\n success: '#16A34A',\n text: '#18181B',\n textAux: '#71717A',\n textMuted: '#A1A1AA',\n} as const;\n\nexport interface TableCanvasPalette {\n accent: string;\n checkboxBg: string;\n fontFamily: string;\n isDarkMode: boolean;\n overlayBorder: string;\n pillBg: string;\n rowHover: string;\n starOff: string;\n starOn: string;\n success: string;\n text: string;\n textAux: string;\n textMuted: string;\n}\n\n/**\n * True when a CSS colour paints fully opaque. parseToRgb reports opaque colours without an\n * alpha at all rather than as 1, and throws on anything it cannot parse (which we treat as\n * not opaque, so callers fall back).\n */\nexport const isOpaqueColor = (color: string): boolean => {\n try {\n const parsed = parseToRgb(color);\n const alpha = 'alpha' in parsed ? parsed.alpha : undefined;\n\n return alpha == null || alpha >= 1;\n } catch {\n return false;\n }\n};\n\n/**\n * Group title rows sit above the rows that scroll beneath them, so their fill has to be\n * opaque — a translucent token would let content show through the sticky title. Keep the\n * token when it is opaque and fall back to the built-in neutral when it is not, so a custom\n * opaque colour still themes the group rows.\n */\nexport const resolveGroupFill = (token?: TableToken): string => {\n const fallback = token?.isDarkMode ? TABLE_CHROME.groupDark : TABLE_CHROME.group;\n const fill = token?.colorFillQuaternary;\n\n if (fill == null) return fallback;\n\n return isOpaqueColor(fill) ? fill : fallback;\n};\n\nexport const resolveTableCanvasPalette = (token?: TableToken): TableCanvasPalette => ({\n accent: token?.colorPrimary ?? TABLE_CHROME.accent,\n fontFamily: token?.fontFamily ?? TABLE_CHROME.fontFamily,\n isDarkMode: Boolean(token?.isDarkMode),\n checkboxBg: token?.colorBgContainer ?? '#FFFFFF',\n overlayBorder: token?.colorBorderSecondary ?? TABLE_CHROME.overlayBorder,\n pillBg: token?.colorFillTertiary ?? TABLE_CHROME.pillBg,\n rowHover: token?.colorFillTertiary ?? TABLE_CHROME.rowHover,\n starOff: token?.colorFillTertiary ?? TABLE_CHROME.starOff,\n starOn: token?.colorTextTertiary ?? TABLE_CHROME.starOn,\n success: token?.colorSuccess ?? TABLE_CHROME.success,\n text: token?.colorText ?? TABLE_CHROME.text,\n textAux: token?.colorTextSecondary ?? TABLE_CHROME.textAux,\n textMuted: token?.colorTextTertiary ?? TABLE_CHROME.textMuted,\n});\n\n/** 画在 `accent` 之上的前景色候选:深、浅各一个,取对比度更高的那个。 */\nconst ON_ACCENT_DARK = '#18181B';\nconst ON_ACCENT_LIGHT = '#FFFFFF';\nconst ON_ACCENT_DARK_RGB: Rgb = { b: 27, g: 24, r: 24 };\nconst ON_ACCENT_LIGHT_RGB: Rgb = { b: 255, g: 255, r: 255 };\n\n/**\n * 画在 `accent` 之上的前景色(对勾、半选短横)。\n *\n * **不能写死白色。** `accent` 来自 `token.colorPrimary`,而主色不一定深 —— 暗色下实测是\n * `#eeeeee`,白对勾画上去对比度只有 **1.1:1**,等于看不见(浅色下 `accent` 是 `#222222`,\n * 白对勾才是对的)。按对比度二选一,两种主题都对。\n */\nexport const resolveOnAccent = (accent: string): string => {\n const rgb = parseHex(accent);\n if (!rgb) return ON_ACCENT_LIGHT;\n\n return contrastRatio(rgb, ON_ACCENT_DARK_RGB) >= contrastRatio(rgb, ON_ACCENT_LIGHT_RGB)\n ? ON_ACCENT_DARK\n : ON_ACCENT_LIGHT;\n};\n\n/** SuperAd rows use a top hairline so separators stay visible; vertical borders are opt-in. */\nconst hairline = [1, 1, 0, 0] as [number, number, number, number];\n/** Default SuperAd table: horizontal top row separator only. */\nconst hairlineNoVertical = [1, 0, 0, 0] as [number, number, number, number];\n\nexport interface CreateTableThemeOptions {\n /** Show vertical borders between columns. The row index column never gets one. */\n showVerticalBorder?: boolean;\n}\n\nexport const createTableTheme = (\n token: TableToken,\n { showVerticalBorder = false }: CreateTableThemeOptions = {},\n): TableTheme => {\n const borderLineWidth = showVerticalBorder ? hairline : hairlineNoVertical;\n const headerBorderLineWidth = showVerticalBorder\n ? ([0, 1, 0, 0] as [number, number, number, number])\n : ([0, 0, 0, 0] as [number, number, number, number]);\n const accent = token.colorPrimary ?? TABLE_CHROME.accent;\n // 冻结列阴影的「墨色」。**不能两套主题共用一个近黑** —— 深色压在同为深色的底上等于没有阴影:\n // 实测暗色下已滚动与未滚动的边界像素完全一样(13,13,13),冻结列与内容之间看不出分界。\n // 浅色这一侧的 RGB 与 alpha 保持原值不动,基线才不会被这次修复带偏。\n const shadowInk = token.isDarkMode ? '255, 255, 255' : '24, 24, 27';\n const shadowAlpha = token.isDarkMode ? 0.14 : 0.06;\n const border = token.colorBorderSecondary ?? TABLE_CHROME.border;\n const headerBorderColor = showVerticalBorder ? border : 'transparent';\n const groupFill = resolveGroupFill(token);\n const text = token.colorText ?? TABLE_CHROME.text;\n const textSecondary = token.colorTextSecondary ?? TABLE_CHROME.textAux;\n const textMuted = token.colorTextTertiary ?? TABLE_CHROME.textMuted;\n const rowHover = token.colorFillTertiary ?? TABLE_CHROME.rowHover;\n\n return {\n bodyStyle: {\n bgColor: token.colorBgContainer,\n borderColor: border,\n borderLineWidth,\n color: text,\n fontFamily: token.fontFamily,\n fontSize: 14,\n hover: {\n cellBgColor: rowHover,\n inlineRowBgColor: rowHover,\n inlineColumnBgColor: rowHover,\n },\n padding: [0, TABLE_CHROME.paddingX, 0, TABLE_CHROME.paddingX],\n textAlign: 'left',\n },\n cellInnerBorder: true,\n columnResize: {\n bgColor: 'transparent',\n labelVisible: false,\n lineColor: 'transparent',\n lineWidth: 0,\n visibleOnHover: false,\n width: 0,\n },\n dragHeaderSplitLine: {\n lineColor: accent,\n lineWidth: 2,\n shadowBlockColor: rgba(accent, 0.08),\n },\n defaultStyle: {\n borderColor: 'transparent',\n borderLineWidth: headerBorderLineWidth,\n fontFamily: token.fontFamily,\n fontSize: 14,\n },\n frozenColumnLine: {\n shadow: {\n width: 8,\n // 两端用同一组 RGB:混用会让渐变中途泛灰。\n startColor: `rgba(${shadowInk}, ${shadowAlpha})`,\n endColor: `rgba(${shadowInk}, 0)`,\n visible: 'scrolling',\n },\n },\n frameStyle: {\n borderColor: border,\n borderLineWidth: [0, 0, 1, 0] as [number, number, number, number],\n cornerRadius: 0,\n },\n functionalIconsStyle: {\n collapse_color: textMuted,\n dragReorder_color: textMuted,\n dragReorder_color_opacity: '1',\n dragReorder_size: 16,\n expand_color: textMuted,\n },\n groupTitleStyle: {\n bgColor: groupFill,\n borderColor: border,\n borderLineWidth,\n color: textSecondary,\n cursor: 'pointer',\n fontFamily: token.fontFamily,\n fontSize: 14,\n fontWeight: 500,\n hover: {\n cellBgColor: rowHover,\n },\n padding: [0, TABLE_CHROME.paddingX, 0, 8],\n select: {\n cellBgColor: groupFill,\n inlineRowBgColor: groupFill,\n },\n textAlign: 'left',\n },\n rowHeaderStyle: {\n hover: {\n cellBgColor: rowHover,\n inlineRowBgColor: rowHover,\n inlineColumnBgColor: rowHover,\n },\n },\n headerStyle: {\n bgColor: token.colorBgContainer,\n borderColor: headerBorderColor,\n borderLineWidth: headerBorderLineWidth,\n color: textSecondary,\n fontFamily: token.fontFamily,\n fontSize: 13,\n fontWeight: 500,\n hover: {\n cellBgColor: rowHover,\n inlineRowBgColor: rowHover,\n inlineColumnBgColor: rowHover,\n },\n padding: [0, TABLE_CHROME.paddingX, 0, TABLE_CHROME.paddingX],\n textAlign: 'left',\n },\n scrollStyle: {\n barToSide: true,\n // 默认**从冻结列右边才开始**,不铺满整宽 —— 有冻结列的表(树表、单元格类型)因此和没有\n // 冻结列的表长得不一样,看起来像「缩在里面」。打开这个开关让横条始终从最左铺到最右。\n ignoreFrozenCols: true,\n // **`hoverOn` 不是「hover 才显示」,而是「滚动条浮在内容上、不占位」。** 开着它时\n // `getHorizontalScrollBarSize` 直接返回 0,引擎**不预留滚动条槽** —— 内容一直铺到画布\n // 底边,而横条固定在画布底边,于是它压在底边框上(内容纵向溢出的表都是这个观感:树形\n // 表格、Table 分组、百万数据)。关掉它引擎才会 `height = heightP - hScrollBarWidth` 预留\n // 那 4px,横条落到边框**下方**的槽里,与不滚动的表(autoHeight 自带 8px 槽)一致。\n //\n // 「什么时候出现」由 `visible: 'focus'` 管,与这一项无关。\n hoverOn: false,\n // 滑块与表格留出 2px 缝:`width` 是**滚动条的槽宽**(引擎按它预留空间),滑块的实际\n // 粗细由 padding 从槽里内缩出来 —— 槽 8、上下(横条)/ 左右(竖条)各内缩 2,滑块仍是\n // 4px,贴着表格那侧因此有 2px 空白。槽宽即内容要付出的宽度,比 4px 多让出 4px。\n horizontalPadding: [2, 0, 2, 0],\n verticalPadding: [0, 2, 0, 2],\n scrollSliderColor: token.colorTextQuaternary || TABLE_CHROME.starOff,\n scrollSliderCornerRadius: 999,\n visible: 'focus',\n // 槽宽(不是滑块粗细)。8 = 滑块 4 + 两侧各 2 的缝,见上面的 padding 注释。\n width: 8,\n },\n selectionStyle: {\n // 选中不画任何高亮:不铺色(铺色和 hover 的中性灰撞)、不描边(这套 token 的主色是\n // 近黑,描出来是一圈黑框)。选中只用来驱动编辑与复制,视觉反馈交给 hover。\n cellBgColor: 'transparent',\n cellBorderColor: 'transparent',\n cellBorderLineWidth: 0,\n selectionFillMode: 'overlay',\n },\n underlayBackgroundColor: token.colorBgContainer,\n };\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"theme.mjs","names":[],"sources":["../../../src/Table/canvas/theme.ts"],"sourcesContent":["import type { Theme } from 'antd-style';\nimport { parseToRgb, rgba } from 'polished';\n\nimport { contrastRatio, parseHex, type Rgb } from '../core/tagTone';\nimport type { TableTheme } from '../type';\n\nexport type TableToken = Pick<\n Theme,\n | 'colorBgContainer'\n | 'colorBorderSecondary'\n | 'colorFillQuaternary'\n | 'colorFillSecondary'\n | 'colorFillTertiary'\n | 'colorInfo'\n | 'colorPrimary'\n | 'colorSplit'\n | 'colorSuccess'\n | 'colorText'\n | 'colorTextQuaternary'\n | 'colorTextSecondary'\n | 'colorTextTertiary'\n | 'fontFamily'\n> & { isDarkMode?: boolean };\n/**\n * SuperAd Table visual language, aligned with the Figma Community kit.\n * White header, light bottom hairlines, 44px rows, neutral group rows and a\n * restrained brand-blue accent. Values are fallbacks; createTableTheme still\n * prefers Ant Design / antd-style tokens where they exist.\n */\nexport const TABLE_CHROME = {\n accent: '#1F5EFF',\n // 与 vrender 内置的默认一致:拿不到 antd token 时行为不变(见 DEFAULT_TEXT_FONT_FAMILY)。\n fontFamily:\n \"PingFang SC,Helvetica Neue,Microsoft Yahei,system-ui,'-apple-system',segoe ui,Roboto,Helvetica,Arial,sans-serif\",\n\n border: '#F4F4F5',\n group: '#FAFAFA',\n groupDark: '#161616',\n hover: '#FAFAFA',\n rowHover: '#F5F5F5',\n overlayBorder: '#E4E4E7',\n paddingX: 8,\n pillBg: '#F4F4F5',\n starOff: '#F4F4F5',\n starOn: '#71717A',\n success: '#16A34A',\n text: '#18181B',\n textAux: '#71717A',\n textMuted: '#A1A1AA',\n} as const;\n\nexport interface TableCanvasPalette {\n accent: string;\n checkboxBg: string;\n fontFamily: string;\n isDarkMode: boolean;\n overlayBorder: string;\n pillBg: string;\n rowHover: string;\n starOff: string;\n starOn: string;\n success: string;\n text: string;\n textAux: string;\n textMuted: string;\n}\n\n/**\n * True when a CSS colour paints fully opaque. parseToRgb reports opaque colours without an\n * alpha at all rather than as 1, and throws on anything it cannot parse (which we treat as\n * not opaque, so callers fall back).\n */\nexport const isOpaqueColor = (color: string): boolean => {\n try {\n const parsed = parseToRgb(color);\n const alpha = 'alpha' in parsed ? parsed.alpha : undefined;\n\n return alpha == null || alpha >= 1;\n } catch {\n return false;\n }\n};\n\n/**\n * Group title rows sit above the rows that scroll beneath them, so their fill has to be\n * opaque — a translucent token would let content show through the sticky title. Keep the\n * token when it is opaque and fall back to the built-in neutral when it is not, so a custom\n * opaque colour still themes the group rows.\n */\nexport const resolveGroupFill = (token?: TableToken): string => {\n const fallback = token?.isDarkMode ? TABLE_CHROME.groupDark : TABLE_CHROME.group;\n const fill = token?.colorFillQuaternary;\n\n if (fill == null) return fallback;\n\n return isOpaqueColor(fill) ? fill : fallback;\n};\n\nexport const resolveTableCanvasPalette = (token?: TableToken): TableCanvasPalette => ({\n accent: token?.colorPrimary ?? TABLE_CHROME.accent,\n fontFamily: token?.fontFamily ?? TABLE_CHROME.fontFamily,\n isDarkMode: Boolean(token?.isDarkMode),\n checkboxBg: token?.colorBgContainer ?? '#FFFFFF',\n overlayBorder: token?.colorBorderSecondary ?? TABLE_CHROME.overlayBorder,\n pillBg: token?.colorFillTertiary ?? TABLE_CHROME.pillBg,\n rowHover: token?.colorFillTertiary ?? TABLE_CHROME.rowHover,\n starOff: token?.colorFillTertiary ?? TABLE_CHROME.starOff,\n starOn: token?.colorTextTertiary ?? TABLE_CHROME.starOn,\n success: token?.colorSuccess ?? TABLE_CHROME.success,\n text: token?.colorText ?? TABLE_CHROME.text,\n textAux: token?.colorTextSecondary ?? TABLE_CHROME.textAux,\n textMuted: token?.colorTextTertiary ?? TABLE_CHROME.textMuted,\n});\n\n/** 画在 `accent` 之上的前景色候选:深、浅各一个,取对比度更高的那个。 */\nconst ON_ACCENT_DARK = '#18181B';\nconst ON_ACCENT_LIGHT = '#FFFFFF';\nconst ON_ACCENT_DARK_RGB: Rgb = { b: 27, g: 24, r: 24 };\nconst ON_ACCENT_LIGHT_RGB: Rgb = { b: 255, g: 255, r: 255 };\n\n/**\n * 画在 `accent` 之上的前景色(对勾、半选短横)。\n *\n * **不能写死白色。** `accent` 来自 `token.colorPrimary`,而主色不一定深 —— 暗色下实测是\n * `#eeeeee`,白对勾画上去对比度只有 **1.1:1**,等于看不见(浅色下 `accent` 是 `#222222`,\n * 白对勾才是对的)。按对比度二选一,两种主题都对。\n */\nexport const resolveOnAccent = (accent: string): string => {\n const rgb = parseHex(accent);\n if (!rgb) return ON_ACCENT_LIGHT;\n\n return contrastRatio(rgb, ON_ACCENT_DARK_RGB) >= contrastRatio(rgb, ON_ACCENT_LIGHT_RGB)\n ? ON_ACCENT_DARK\n : ON_ACCENT_LIGHT;\n};\n\n/** SuperAd rows use a top hairline so separators stay visible; vertical borders are opt-in. */\nconst hairlineNoVertical = [1, 0, 0, 0] as [number, number, number, number];\n\nexport type BorderLineWidth = [number, number, number, number];\n\nexport interface VerticalBorderCell {\n col: number;\n row?: number;\n table?: {\n colCount?: number;\n getCellRange?: (\n col: number,\n row: number,\n ) => { end?: { col?: number }; start?: { col?: number } } | undefined;\n };\n}\n\n/**\n * `showVerticalBorder` 打开时的列竖线:[top, right, bottom, left]。\n *\n * 第一列(含序号列)左右都不画,因此序号列右侧那条缝也不出现。\n * 最后一列不画右边(表外沿);它的左边用来画出与前一列的内缝。\n * 其余列左右都开。\n *\n * 竖线必须落在单元格**左边**。VTable 默认 `cellBorderClipDirection` 是 `top-left`,\n * 1px 右边框会画进下一格半像素,被下一格底色盖住 —— 只开右边时中间列看起来像没竖线。\n * 不改 clip 方向:改成 `bottom-right` 会把现在靠上边画的行间发丝线盖掉。\n */\nexport const resolveVerticalBorderLineWidth = (\n cell: VerticalBorderCell,\n top: number,\n showVerticalBorder: boolean,\n): BorderLineWidth => {\n if (!showVerticalBorder) return [top, 0, 0, 0];\n\n const colCount = Math.max(1, cell.table?.colCount ?? 1);\n const last = colCount - 1;\n const range = cell.table?.getCellRange?.(cell.col, cell.row ?? 0);\n const start = range?.start?.col ?? cell.col;\n const end = range?.end?.col ?? cell.col;\n const isFirst = start <= 0;\n const isLast = end >= last;\n\n if (colCount <= 1 || isFirst) return [top, 0, 0, 0];\n\n // 紧挨第一列 / 序号列的那条缝不画(第一列右边按产品要求关闭)。\n const left = start > 1 ? 1 : 0;\n\n return [top, isLast ? 0 : 1, 0, left];\n};\n\nexport interface CreateTableThemeOptions {\n /** Show vertical borders between columns. First / last columns omit their left and right edges. */\n showVerticalBorder?: boolean;\n}\n\nexport const createTableTheme = (\n token: TableToken,\n { showVerticalBorder = false }: CreateTableThemeOptions = {},\n): TableTheme => {\n const borderLineWidth = showVerticalBorder\n ? (args: VerticalBorderCell) => resolveVerticalBorderLineWidth(args, 1, true)\n : hairlineNoVertical;\n const headerBorderLineWidth = showVerticalBorder\n ? (args: VerticalBorderCell) => resolveVerticalBorderLineWidth(args, 0, true)\n : ([0, 0, 0, 0] as BorderLineWidth);\n const accent = token.colorPrimary ?? TABLE_CHROME.accent;\n // 冻结列阴影的「墨色」。**不能两套主题共用一个近黑** —— 深色压在同为深色的底上等于没有阴影:\n // 实测暗色下已滚动与未滚动的边界像素完全一样(13,13,13),冻结列与内容之间看不出分界。\n // 浅色这一侧的 RGB 与 alpha 保持原值不动,基线才不会被这次修复带偏。\n const shadowInk = token.isDarkMode ? '255, 255, 255' : '24, 24, 27';\n const shadowAlpha = token.isDarkMode ? 0.14 : 0.06;\n const border = token.colorBorderSecondary ?? TABLE_CHROME.border;\n const headerBorderColor = showVerticalBorder ? border : 'transparent';\n const groupFill = resolveGroupFill(token);\n const text = token.colorText ?? TABLE_CHROME.text;\n const textSecondary = token.colorTextSecondary ?? TABLE_CHROME.textAux;\n const textMuted = token.colorTextTertiary ?? TABLE_CHROME.textMuted;\n const rowHover = token.colorFillTertiary ?? TABLE_CHROME.rowHover;\n\n return {\n bodyStyle: {\n bgColor: token.colorBgContainer,\n borderColor: border,\n borderLineWidth,\n color: text,\n fontFamily: token.fontFamily,\n fontSize: 14,\n hover: {\n cellBgColor: rowHover,\n inlineRowBgColor: rowHover,\n inlineColumnBgColor: rowHover,\n },\n padding: [0, TABLE_CHROME.paddingX, 0, TABLE_CHROME.paddingX],\n textAlign: 'left',\n },\n cellInnerBorder: true,\n columnResize: {\n bgColor: 'transparent',\n labelVisible: false,\n lineColor: 'transparent',\n lineWidth: 0,\n visibleOnHover: false,\n width: 0,\n },\n dragHeaderSplitLine: {\n lineColor: accent,\n lineWidth: 2,\n shadowBlockColor: rgba(accent, 0.08),\n },\n defaultStyle: {\n borderColor: 'transparent',\n borderLineWidth: headerBorderLineWidth,\n fontFamily: token.fontFamily,\n fontSize: 14,\n },\n frozenColumnLine: {\n shadow: {\n width: 8,\n // 两端用同一组 RGB:混用会让渐变中途泛灰。\n startColor: `rgba(${shadowInk}, ${shadowAlpha})`,\n endColor: `rgba(${shadowInk}, 0)`,\n visible: 'scrolling',\n },\n },\n frameStyle: {\n borderColor: border,\n borderLineWidth: [0, 0, 1, 0] as [number, number, number, number],\n cornerRadius: 0,\n },\n functionalIconsStyle: {\n collapse_color: textMuted,\n dragReorder_color: textMuted,\n dragReorder_color_opacity: '1',\n dragReorder_size: 16,\n expand_color: textMuted,\n },\n groupTitleStyle: {\n bgColor: groupFill,\n borderColor: border,\n borderLineWidth,\n color: textSecondary,\n cursor: 'pointer',\n fontFamily: token.fontFamily,\n fontSize: 14,\n fontWeight: 500,\n hover: {\n cellBgColor: rowHover,\n },\n padding: [0, TABLE_CHROME.paddingX, 0, 8],\n select: {\n cellBgColor: groupFill,\n inlineRowBgColor: groupFill,\n },\n textAlign: 'left',\n },\n rowHeaderStyle: {\n hover: {\n cellBgColor: rowHover,\n inlineRowBgColor: rowHover,\n inlineColumnBgColor: rowHover,\n },\n },\n headerStyle: {\n bgColor: token.colorBgContainer,\n borderColor: headerBorderColor,\n borderLineWidth: headerBorderLineWidth,\n color: textSecondary,\n fontFamily: token.fontFamily,\n fontSize: 13,\n fontWeight: 500,\n hover: {\n cellBgColor: rowHover,\n inlineRowBgColor: rowHover,\n inlineColumnBgColor: rowHover,\n },\n padding: [0, TABLE_CHROME.paddingX, 0, TABLE_CHROME.paddingX],\n textAlign: 'left',\n },\n scrollStyle: {\n barToSide: true,\n // 默认**从冻结列右边才开始**,不铺满整宽 —— 有冻结列的表(树表、单元格类型)因此和没有\n // 冻结列的表长得不一样,看起来像「缩在里面」。打开这个开关让横条始终从最左铺到最右。\n ignoreFrozenCols: true,\n // **`hoverOn` 不是「hover 才显示」,而是「滚动条浮在内容上、不占位」。** 开着它时\n // `getHorizontalScrollBarSize` 直接返回 0,引擎**不预留滚动条槽** —— 内容一直铺到画布\n // 底边,而横条固定在画布底边,于是它压在底边框上(内容纵向溢出的表都是这个观感:树形\n // 表格、Table 分组、百万数据)。关掉它引擎才会 `height = heightP - hScrollBarWidth` 预留\n // 那 4px,横条落到边框**下方**的槽里,与不滚动的表(autoHeight 自带 8px 槽)一致。\n //\n // 「什么时候出现」由 `visible: 'focus'` 管,与这一项无关。\n hoverOn: false,\n // 滑块与表格留出 2px 缝:`width` 是**滚动条的槽宽**(引擎按它预留空间),滑块的实际\n // 粗细由 padding 从槽里内缩出来 —— 槽 8、上下(横条)/ 左右(竖条)各内缩 2,滑块仍是\n // 4px,贴着表格那侧因此有 2px 空白。槽宽即内容要付出的宽度,比 4px 多让出 4px。\n horizontalPadding: [2, 0, 2, 0],\n verticalPadding: [0, 2, 0, 2],\n scrollSliderColor: token.colorTextQuaternary || TABLE_CHROME.starOff,\n scrollSliderCornerRadius: 999,\n visible: 'focus',\n // 槽宽(不是滑块粗细)。8 = 滑块 4 + 两侧各 2 的缝,见上面的 padding 注释。\n width: 8,\n },\n selectionStyle: {\n // 选中不画任何高亮:不铺色(铺色和 hover 的中性灰撞)、不描边(这套 token 的主色是\n // 近黑,描出来是一圈黑框)。选中只用来驱动编辑与复制,视觉反馈交给 hover。\n cellBgColor: 'transparent',\n cellBorderColor: 'transparent',\n cellBorderLineWidth: 0,\n selectionFillMode: 'overlay',\n },\n underlayBackgroundColor: token.colorBgContainer,\n };\n};\n"],"mappings":";;;;;;;;;AA6BA,MAAa,eAAe;CAC1B,QAAQ;CAER,YACE;CAEF,QAAQ;CACR,OAAO;CACP,WAAW;CACX,OAAO;CACP,UAAU;CACV,eAAe;CACf,UAAU;CACV,QAAQ;CACR,SAAS;CACT,QAAQ;CACR,SAAS;CACT,MAAM;CACN,SAAS;CACT,WAAW;AACb;;;;;;AAuBA,MAAa,iBAAiB,UAA2B;CACvD,IAAI;EACF,MAAM,SAAS,WAAW,KAAK;EAC/B,MAAM,QAAQ,WAAW,SAAS,OAAO,QAAQ,KAAA;EAEjD,OAAO,SAAS,QAAQ,SAAS;CACnC,QAAQ;EACN,OAAO;CACT;AACF;;;;;;;AAQA,MAAa,oBAAoB,UAA+B;CAC9D,MAAM,WAAW,OAAO,aAAa,aAAa,YAAY,aAAa;CAC3E,MAAM,OAAO,OAAO;CAEpB,IAAI,QAAQ,MAAM,OAAO;CAEzB,OAAO,cAAc,IAAI,IAAI,OAAO;AACtC;AAEA,MAAa,6BAA6B,WAA4C;CACpF,QAAQ,OAAO,gBAAgB,aAAa;CAC5C,YAAY,OAAO,cAAc,aAAa;CAC9C,YAAY,QAAQ,OAAO,UAAU;CACrC,YAAY,OAAO,oBAAoB;CACvC,eAAe,OAAO,wBAAwB,aAAa;CAC3D,QAAQ,OAAO,qBAAqB,aAAa;CACjD,UAAU,OAAO,qBAAqB,aAAa;CACnD,SAAS,OAAO,qBAAqB,aAAa;CAClD,QAAQ,OAAO,qBAAqB,aAAa;CACjD,SAAS,OAAO,gBAAgB,aAAa;CAC7C,MAAM,OAAO,aAAa,aAAa;CACvC,SAAS,OAAO,sBAAsB,aAAa;CACnD,WAAW,OAAO,qBAAqB,aAAa;AACtD;;AAGA,MAAM,iBAAiB;AACvB,MAAM,kBAAkB;AACxB,MAAM,qBAA0B;CAAE,GAAG;CAAI,GAAG;CAAI,GAAG;AAAG;AACtD,MAAM,sBAA2B;CAAE,GAAG;CAAK,GAAG;CAAK,GAAG;AAAI;;;;;;;;AAS1D,MAAa,mBAAmB,WAA2B;CACzD,MAAM,MAAM,SAAS,MAAM;CAC3B,IAAI,CAAC,KAAK,OAAO;CAEjB,OAAO,cAAc,KAAK,kBAAkB,KAAK,cAAc,KAAK,mBAAmB,IACnF,iBACA;AACN;;AAGA,MAAM,qBAAqB;CAAC;CAAG;CAAG;CAAG;AAAC;;;;;;;;;;;;AA2BtC,MAAa,kCACX,MACA,KACA,uBACoB;CACpB,IAAI,CAAC,oBAAoB,OAAO;EAAC;EAAK;EAAG;EAAG;CAAC;CAE7C,MAAM,WAAW,KAAK,IAAI,GAAG,KAAK,OAAO,YAAY,CAAC;CACtD,MAAM,OAAO,WAAW;CACxB,MAAM,QAAQ,KAAK,OAAO,eAAe,KAAK,KAAK,KAAK,OAAO,CAAC;CAChE,MAAM,QAAQ,OAAO,OAAO,OAAO,KAAK;CACxC,MAAM,MAAM,OAAO,KAAK,OAAO,KAAK;CACpC,MAAM,UAAU,SAAS;CACzB,MAAM,SAAS,OAAO;CAEtB,IAAI,YAAY,KAAK,SAAS,OAAO;EAAC;EAAK;EAAG;EAAG;CAAC;CAKlD,OAAO;EAAC;EAAK,SAAS,IAAI;EAAG;EAFhB,QAAQ,IAAI,IAAI;CAEO;AACtC;AAOA,MAAa,oBACX,OACA,EAAE,qBAAqB,UAAmC,CAAC,MAC5C;CACf,MAAM,kBAAkB,sBACnB,SAA6B,+BAA+B,MAAM,GAAG,IAAI,IAC1E;CACJ,MAAM,wBAAwB,sBACzB,SAA6B,+BAA+B,MAAM,GAAG,IAAI,IACzE;EAAC;EAAG;EAAG;EAAG;CAAC;CAChB,MAAM,SAAS,MAAM,gBAAgB,aAAa;CAIlD,MAAM,YAAY,MAAM,aAAa,kBAAkB;CACvD,MAAM,cAAc,MAAM,aAAa,MAAO;CAC9C,MAAM,SAAS,MAAM,wBAAwB,aAAa;CAC1D,MAAM,oBAAoB,qBAAqB,SAAS;CACxD,MAAM,YAAY,iBAAiB,KAAK;CACxC,MAAM,OAAO,MAAM,aAAa,aAAa;CAC7C,MAAM,gBAAgB,MAAM,sBAAsB,aAAa;CAC/D,MAAM,YAAY,MAAM,qBAAqB,aAAa;CAC1D,MAAM,WAAW,MAAM,qBAAqB,aAAa;CAEzD,OAAO;EACL,WAAW;GACT,SAAS,MAAM;GACf,aAAa;GACb;GACA,OAAO;GACP,YAAY,MAAM;GAClB,UAAU;GACV,OAAO;IACL,aAAa;IACb,kBAAkB;IAClB,qBAAqB;GACvB;GACA,SAAS;IAAC;IAAG,aAAa;IAAU;IAAG,aAAa;GAAQ;GAC5D,WAAW;EACb;EACA,iBAAiB;EACjB,cAAc;GACZ,SAAS;GACT,cAAc;GACd,WAAW;GACX,WAAW;GACX,gBAAgB;GAChB,OAAO;EACT;EACA,qBAAqB;GACnB,WAAW;GACX,WAAW;GACX,kBAAkB,KAAK,QAAQ,GAAI;EACrC;EACA,cAAc;GACZ,aAAa;GACb,iBAAiB;GACjB,YAAY,MAAM;GAClB,UAAU;EACZ;EACA,kBAAkB,EAChB,QAAQ;GACN,OAAO;GAEP,YAAY,QAAQ,UAAU,IAAI,YAAY;GAC9C,UAAU,QAAQ,UAAU;GAC5B,SAAS;EACX,EACF;EACA,YAAY;GACV,aAAa;GACb,iBAAiB;IAAC;IAAG;IAAG;IAAG;GAAC;GAC5B,cAAc;EAChB;EACA,sBAAsB;GACpB,gBAAgB;GAChB,mBAAmB;GACnB,2BAA2B;GAC3B,kBAAkB;GAClB,cAAc;EAChB;EACA,iBAAiB;GACf,SAAS;GACT,aAAa;GACb;GACA,OAAO;GACP,QAAQ;GACR,YAAY,MAAM;GAClB,UAAU;GACV,YAAY;GACZ,OAAO,EACL,aAAa,SACf;GACA,SAAS;IAAC;IAAG,aAAa;IAAU;IAAG;GAAC;GACxC,QAAQ;IACN,aAAa;IACb,kBAAkB;GACpB;GACA,WAAW;EACb;EACA,gBAAgB,EACd,OAAO;GACL,aAAa;GACb,kBAAkB;GAClB,qBAAqB;EACvB,EACF;EACA,aAAa;GACX,SAAS,MAAM;GACf,aAAa;GACb,iBAAiB;GACjB,OAAO;GACP,YAAY,MAAM;GAClB,UAAU;GACV,YAAY;GACZ,OAAO;IACL,aAAa;IACb,kBAAkB;IAClB,qBAAqB;GACvB;GACA,SAAS;IAAC;IAAG,aAAa;IAAU;IAAG,aAAa;GAAQ;GAC5D,WAAW;EACb;EACA,aAAa;GACX,WAAW;GAGX,kBAAkB;GAQlB,SAAS;GAIT,mBAAmB;IAAC;IAAG;IAAG;IAAG;GAAC;GAC9B,iBAAiB;IAAC;IAAG;IAAG;IAAG;GAAC;GAC5B,mBAAmB,MAAM,uBAAuB,aAAa;GAC7D,0BAA0B;GAC1B,SAAS;GAET,OAAO;EACT;EACA,gBAAgB;GAGd,aAAa;GACb,iBAAiB;GACjB,qBAAqB;GACrB,mBAAmB;EACrB;EACA,yBAAyB,MAAM;CACjC;AACF"}
@@ -248,11 +248,15 @@ var ReactEditor = class {
248
248
  onEnd() {
249
249
  if (this.kind === "picker") markPickerClosed(this.table);
250
250
  document.removeEventListener("pointerdown", this.onDocumentPointerDown, true);
251
- this.root?.unmount();
252
- detach(this.mount);
251
+ const root = this.root;
252
+ const mount = this.mount;
253
253
  this.root = void 0;
254
254
  this.mount = void 0;
255
255
  this.endEdit = void 0;
256
+ queueMicrotask(() => {
257
+ root?.unmount();
258
+ detach(mount);
259
+ });
256
260
  }
257
261
  isEditorElement(target) {
258
262
  return Boolean(this.mount?.contains(target) || typeof target?.closest === "function" && target.closest(POPUP_SELECTOR));
@@ -1 +1 @@
1
- {"version":3,"file":"base.mjs","names":[],"sources":["../../../../src/Table/react/editors/base.tsx"],"sourcesContent":["'use client';\n\nimport {\n type EditContext,\n type IEditor,\n type RectProps,\n ValidateEnum,\n} from '@visactor/vtable-editors';\nimport { ConfigProvider, Empty, Input, type ThemeConfig } from 'antd';\nimport { type ReactNode } from 'react';\nimport { createRoot, type Root } from 'react-dom/client';\n\nimport { copyToClipboard } from '@/utils/copyToClipboard';\n\nimport { antdLocaleFor, readTableI18n, tableText } from '../../core/tableI18n';\nimport { updateCellContent, updateNextFrame } from '../../vtable/vtableAdapter';\nimport { styles } from '../style';\n\nexport type EditorKind = 'inline' | 'picker';\n\nexport interface TableEditor {\n kind: EditorKind;\n}\n\n/** 按表记录:模块级单例会让一张表关掉 picker 之后,另一张表 160ms 内的点击被误吞。 */\nconst pickerClosedAt = new WeakMap<object, number>();\n\n/**\n * 编辑器里的 Ctrl/Cmd+A 必须**留在输入框里**。\n *\n * 引擎在 `table.getElement()` 上监听 keydown(编辑器 DOM 正是它的子节点),见到这个组合就\n * `preventDefault()` + `deelTableSelectAll()`:输入框里的文本永远选不中,紧接着的输入是\n * **插到光标处**而不是替换 —— 数字列会写出用户没打算写的值,JSON 列直接判非法。引擎自带的\n * `InputEditor` 专门为它加了 `stopPropagation()`,我们的编辑器要对齐。\n *\n * 监听挂在编辑器根节点的**冒泡**阶段:事件先到输入框自己,再被这里截住,不再往上走到表元素。\n */\nexport const keepSelectAllInEditor = (root: HTMLElement): void => {\n root.addEventListener('keydown', (event) => {\n if ((event.ctrlKey || event.metaKey) && (event.key === 'a' || event.key === 'A')) {\n event.stopPropagation();\n }\n });\n};\n\nexport const markPickerClosed = (table?: unknown) => {\n if (table == null || typeof table !== 'object') return;\n pickerClosedAt.set(table as object, Date.now());\n};\n\nexport const pickerJustClosed = (table?: unknown) => {\n if (table == null || typeof table !== 'object') return false;\n const at = pickerClosedAt.get(table as object);\n return at != null && Date.now() - at < 160;\n};\n\nconst POPUP_SELECTOR = '.ant-picker-dropdown, .ant-picker-panel-container, .ant-picker-footer';\n\n/** 浮层离容器左右边缘留出的缝。贴边看起来像是被裁掉。 */\nconst EDGE_GUTTER = 8;\n\n/**\n * 水平方向把浮层收进容器:右缘放不下先向左回移,回移到头还放不下就收窄。\n *\n * `rect` 是相对**容器**(VTable 传进来的 container 就是 `table.getElement()`)的,所以夹取的\n * 右界取 `container.clientWidth`,与 `left` 同源。保的是「在容器可视宽度内展示全」,不是视口。\n *\n * **调用方必须先把浮层挂进容器**,否则这里读不到 `parentElement`,夹取被整条跳过。\n */\nconst clampHorizontal = (\n element: HTMLElement,\n rect: RectProps,\n desiredWidth: number,\n): { left: number; width: number } => {\n const containerWidth = element.parentElement?.clientWidth;\n const left = rect.left - 1;\n if (containerWidth == null || containerWidth <= 0) return { left, width: desiredWidth };\n\n // 容器比浮层还窄(窄容器 / 窄视口里的表)时只能收窄:光回移左缘也顶不住,右侧照样探出去。\n const width = Math.min(desiredWidth, Math.max(containerWidth - EDGE_GUTTER * 2, 1));\n const maxLeft = Math.max(containerWidth - width - EDGE_GUTTER, EDGE_GUTTER);\n\n return { left: Math.min(left, maxLeft), width };\n};\n\nexport const applyOverlayBox = (element: HTMLElement, rect: RectProps) => {\n const { left, width } = clampHorizontal(element, rect, Math.max(rect.width + 2, 240));\n element.style.top = `${rect.top - 1}px`;\n element.style.left = `${left}px`;\n element.style.width = `${width}px`;\n element.style.minHeight = `${Math.max(rect.height, 32)}px`;\n element.style.height = `${Math.max(rect.height, 32)}px`;\n};\n\n/**\n * `rect` 是**相对表格**的,而 `window.innerHeight` 是视口单位 —— 翻转判断必须加上表格自己在\n * 视口里的位置,否则两者不同源、算式再对也不成立。\n *\n * 曾经这里取的是 `element.parentElement` 的 top。挂载点未必就是 `rect` 的基准元素(VTable 传进来\n * 的 container 与表格元素不是同一个),实测面板超出视口 105px 却完全不翻转 —— 而单看算式\n * (`containerTop + belowTop + estimatedHeight > innerHeight`)是满足翻转条件的。\n */\n/** 面板的最大高度,翻转判断按它估高(见 `placePicker`)。**必须与 `styles.picker` 的\n * `max-height` 一致**:`placePicker` 优先读计算样式,这里是读不到时的兜底。 */\nconst PANEL_MAX_HEIGHT = 320;\n\nexport const placePicker = (element: HTMLElement, rect: RectProps, minWidth = 240, baseTop = 0) => {\n const { left, width } = clampHorizontal(element, rect, Math.max(rect.width + 2, minWidth));\n // 用面板的 max-height 估高,**不能用 `scrollHeight`**:`attach` 刚把高度设成单元格高度,\n // 而且 React 的首次渲染还没落地 —— 此刻 scrollHeight 是 36 之类的中间值(不是 0,所以连\n // `|| 240` 的兜底都救不回来),翻转判断因此算出「放得下」,而实际超出视口 105px 却完全不翻。\n const estimatedHeight =\n Number.parseFloat(getComputedStyle(element).maxHeight) || PANEL_MAX_HEIGHT;\n const belowTop = rect.top + rect.height + 4;\n let top = belowTop;\n\n if (typeof window !== 'undefined' && baseTop + belowTop + estimatedHeight > window.innerHeight) {\n top = Math.max(rect.top - estimatedHeight - 4, 0);\n }\n\n element.style.top = `${top}px`;\n element.style.left = `${left}px`;\n // min-width 跟着夹取后的宽度写:照 minWidth 原样写会在窄容器里把面板重新顶宽。\n element.style.minWidth = `${width}px`;\n element.style.width = `${width}px`;\n element.style.height = 'auto';\n element.style.maxHeight = `${PANEL_MAX_HEIGHT}px`;\n};\n\nexport const detach = (element?: HTMLElement) => {\n element?.parentElement?.removeChild(element);\n};\n\nconst COPY_ICON =\n '<svg fill=\"none\" height=\"14\" viewBox=\"0 0 16 16\" width=\"14\"><rect height=\"8\" rx=\"1.5\" stroke=\"currentColor\" width=\"8\" x=\"5\" y=\"5\"/><path d=\"M3 11V4.5A1.5 1.5 0 0 1 4.5 3H11\" stroke=\"currentColor\" stroke-linecap=\"round\"/></svg>';\n\nexport const createCopyButton = (getValue: () => string, table?: unknown) => {\n const button = document.createElement('button');\n button.type = 'button';\n button.className = styles.copyButton;\n button.setAttribute('aria-label', tableText(table, 'copy'));\n button.innerHTML = COPY_ICON;\n button.addEventListener('mousedown', (event) => event.preventDefault());\n button.addEventListener('click', (event) => {\n event.preventDefault();\n event.stopPropagation();\n void copyToClipboard(getValue());\n });\n\n return button;\n};\n\n/** 提交被拒时打在编辑器宿主上的属性,样式见 `styles.editor`。 */\nexport const INVALID_ATTR = 'data-superad-invalid';\n\n/**\n * 提交被拒时给出**可见**反馈。静默拒绝和静默改型一样糟:用户按了回车,什么都没发生。\n */\nexport const markEditorInvalid = (host?: HTMLElement): void => {\n host?.setAttribute(INVALID_ATTR, '');\n};\n\n/** 重新编辑即清掉非法态,否则用户改对了还是红的。 */\nexport const watchEditorValidity = (host: HTMLElement, input: HTMLElement): void => {\n input.addEventListener('input', () => host.removeAttribute(INVALID_ATTR));\n};\n\n/**\n * 点在编辑器**外面**就提交(inline 编辑器用,picker 走自己的 `onDocumentPointerDown`)。\n *\n * 引擎的提交手势只有三类,**全部绑在 canvas 或表元素上**:canvas 上的 pointerdown、\n * `table.getElement()` 上的 Enter/Tab/Escape/方向键。于是「打开编辑器 → 输入 → 点页面别处」\n * 这条路什么都不触发:编辑器既不提交也不关闭,用户以为已经保存。表随后被卸载/更新时走的是\n * `cancelEditCell`(不写值),输入就此**静默丢失**。\n *\n * 返回解绑函数,`onEnd` 里调用。\n */\nexport const watchOutsideCommit = (host: HTMLElement, getTable: () => unknown): (() => void) => {\n const onPointerDown = (event: PointerEvent) => {\n if (host.contains(event.target as Node)) return;\n\n (getTable() as { completeEditCell?: () => void } | undefined)?.completeEditCell?.();\n };\n\n document.addEventListener('pointerdown', onPointerDown, true);\n\n return () => document.removeEventListener('pointerdown', onPointerDown, true);\n};\n\n/**\n * 编辑器渲染在**自己的 React root** 里(`createRoot(mount)`,由 VTable 的编辑器协议驱动),\n * 而 React context 不跨 root —— 这里这个 `ConfigProvider` 拿不到外层 `ThemeProvider` 的主题,\n * 不显式传 `theme` 就会退回 antd 默认(主色 `#1677ff` 的蓝)。\n *\n * 实测:日期面板的「此刻 / 确定」按钮与选中日期都是 `rgb(22,119,255)`,而本项目的\n * `--ant-color-primary` 是 `#222222`(近黑)。同一个 root 里所有 antd 组件(Input / Empty /\n * Avatar…)都受这一条影响。主题由 `table.__superadTheme` 送进来,与 canvas 调色板走同一条通道\n * (见 `useTableRenderState`)。\n *\n * 弹层仍然 portal 到 `document.body`:挂进表格容器的话,鼠标扫过弹层会命中表格的\n * pointermove,凭空点亮 hover 悬浮框。\n */\nconst wrapEditor = (node: ReactNode, theme?: ThemeConfig, locale?: string) => (\n <ConfigProvider\n getPopupContainer={() => document.body}\n locale={antdLocaleFor(locale)}\n theme={theme}\n >\n {node}\n </ConfigProvider>\n);\n\n/**\n * 提交时**自己补写一次**。\n *\n * 引擎在「分组 + 树」的**嵌套行**上写入会失败:`_CachedDataSource` 映射不到记录,\n * `changeCellValues` **静默无操作**(`changeCellValue` 则直接抛\n * `TypeError: Cannot read properties of undefined (reading 'toString')`)。\n * 症状是「编辑完值不见了」,而且整张表和原始 records 里都找不到它 —— 触发条件是\n * **有子节点的行**,以及**嵌在它们下面的行**,平铺表完全不受影响。\n *\n * 这里按 `getCellOriginRecord` 拿到记录对象直接写;调用方再返回 `invalidateExit`,\n * 引擎会 `setValue(旧值)` + `doExit()` —— 它自己那次写入本来就会失败,不影响我们写进去\n * 的值。正常行上是同值重写,无害。\n *\n * `invalidateExit` 会调 `setValue` 回滚,值已由这里写好,所以调用方给个空实现即可。\n *\n * 注意:inline 那三个编辑器**没有继承 `ReactEditor`**(它们是独立类、只 implements 接口),\n * 所以这里是自由函数而不是基类方法 —— 只挂在基类上会漏掉它们。\n */\nexport const writeBackOnCommit = (editor: {\n getValue: () => unknown;\n table?: unknown;\n cancelled?: boolean;\n}): void => {\n const table = editor.table as\n | {\n editorManager?: { editCell?: { col: number; row: number } };\n getBodyColumnDefine?: (col: number, row: number) => { field?: string } | undefined;\n getCellOriginRecord?: (col: number, row: number) => Record<string, unknown> | undefined;\n }\n | undefined;\n const cell = table?.editorManager?.editCell;\n\n if (table == null || cell == null || editor.cancelled === true) return;\n\n const record = table.getCellOriginRecord?.(cell.col, cell.row);\n const field = table.getBodyColumnDefine?.(cell.col, cell.row)?.field;\n const value = editor.getValue();\n\n if (record != null && field != null && record[field] !== value) {\n record[field] = value;\n updateCellContent(table as never, cell.col, cell.row);\n updateNextFrame(table as never);\n }\n};\n\nexport abstract class ReactEditor<V> implements IEditor<V>, TableEditor {\n kind: EditorKind = 'inline';\n protected current!: V;\n protected original!: V;\n protected cancelled = false;\n protected ended = false;\n protected endEdit?: () => void;\n protected mount?: HTMLDivElement;\n /** picker 关闭要靠它标明是哪张表,所以 onStart 里要把它存下来。 */\n protected table?: unknown;\n private root?: Root;\n\n abstract onStart(context: EditContext<V>): void;\n\n protected attach(container: HTMLElement, rect: RectProps, className = styles.editor) {\n const mount = document.createElement('div');\n mount.className = className;\n\n // 先挂进容器再定位:applyOverlayBox / placePicker 的水平夹取要读 parentElement.clientWidth。\n container.appendChild(mount);\n applyOverlayBox(mount, rect);\n // 面板里的搜索框 / antd 日期输入框都在表元素内部,Ctrl+A 同样会被引擎吞掉(见\n // `keepSelectAllInEditor`)。\n keepSelectAllInEditor(mount);\n document.addEventListener('pointerdown', this.onDocumentPointerDown, true);\n\n this.mount = mount;\n this.root = createRoot(mount);\n }\n\n /**\n * 提交前的收尾钩子。`ArrayEditor` 用它把**还没回车**的草稿收进值里:document 的捕获\n * pointerdown 先于输入框的 blur 跑,等到 blur 再 `commitDraft` 已经晚了(编辑器已结束)。\n */\n protected beforeFinish(): void {}\n\n private onDocumentPointerDown = (event: PointerEvent) => {\n if (this.mount && this.isEditorElement(event.target as HTMLElement) === false) {\n // 点面板外 = 提交,不是取消。inline 编辑器在 VTable 那边走的是 completeEdit(提交),\n // picker 若在这里判取消,多选(tag / user 默认 multiple)就完全没有提交手势 —— 每勾\n // 一项只重绘不 finish,用户点走即整份丢弃。取消只剩 Escape。\n this.beforeFinish();\n this.finish();\n }\n };\n\n validateValue(): ValidateEnum {\n writeBackOnCommit(this as never);\n return ValidateEnum.invalidateExit;\n }\n\n /** `invalidateExit` 会调它回滚,值已由 `writeBackOnCommit` 写好 —— 空实现即可。 */\n setValue(): void {}\n\n protected paint(node: ReactNode) {\n this.root?.render(wrapEditor(node, this.antdTheme(), readTableI18n(this.table).locale));\n }\n\n /**\n * 当前主题。由 `createTableOption` 写进 `customConfig`(与 `__superadPalette` 同一条通道),\n * 主题变化时随 option 重建一起更新。\n */\n protected antdTheme(): ThemeConfig | undefined {\n return (\n this.table as\n | {\n __superadTheme?: ThemeConfig;\n options?: { customConfig?: { __superadTheme?: ThemeConfig } };\n }\n | undefined\n )?.options?.customConfig?.__superadTheme;\n }\n\n /** 表格元素在视口里的顶边 —— `rect` 是相对表格的,翻转判断要拿它换算。 */\n protected tableTop(): number {\n const element = (this.table as { getElement?: () => HTMLElement } | undefined)?.getElement?.();\n return element ? element.getBoundingClientRect().top : 0;\n }\n\n protected finish() {\n if (this.ended) return;\n this.ended = true;\n this.endEdit?.();\n }\n\n getValue() {\n return this.cancelled ? this.original : this.current;\n }\n\n onEnd() {\n if (this.kind === 'picker') markPickerClosed(this.table);\n document.removeEventListener('pointerdown', this.onDocumentPointerDown, true);\n this.root?.unmount();\n detach(this.mount);\n this.root = undefined;\n this.mount = undefined;\n this.endEdit = undefined;\n }\n\n isEditorElement(target: HTMLElement) {\n return Boolean(\n this.mount?.contains(target) ||\n (typeof target?.closest === 'function' && target.closest(POPUP_SELECTOR)),\n );\n }\n\n adjustPosition(rect: RectProps) {\n if (this.mount) applyOverlayBox(this.mount, rect);\n }\n}\n\n/**\n * Shared picker for list-backed editors (options, people, …). The panel, the search\n * box and the keyboard handling are identical across them, so they live here once and\n * each editor supplies only its item shape through the hooks below.\n */\nexport abstract class ListPickerEditor<TItem, V> extends ReactEditor<V> {\n kind: EditorKind = 'picker';\n protected activeIndex = 0;\n protected multiple: boolean;\n protected query = '';\n protected selected: TItem[] = [];\n\n constructor(multiple = false) {\n super();\n this.multiple = multiple;\n }\n\n /** Every candidate, before the search query is applied. */\n protected abstract candidates(): TItem[];\n /** The items a cell value names. */\n protected abstract fromValue(value: V): TItem[];\n /** Stable identity, used for React keys and for equality. */\n protected abstract keyOf(item: TItem): string;\n protected abstract matches(item: TItem, query: string): boolean;\n /** 候选列表里一行长什么样。已选项只靠行尾的 ✓ 标出来,面板顶部不再另起一排 chip。 */\n protected abstract renderRow(item: TItem): ReactNode;\n /** The value the cell keeps for the current selection. */\n protected abstract publish(items: TItem[]): V;\n\n /** 搜索框的占位文案。各 picker 自己说清楚搜的是什么。 */\n protected searchPlaceholder(): string {\n return tableText(this.table, 'search');\n }\n\n /**\n * 面板的最小宽度。默认 240(Notion 人员 / 选项浮层实测宽度);\n * 地址表单要更宽,因为它是两列字段。\n */\n protected panelMinWidth(): number {\n return 240;\n }\n\n onStart({ container, endEdit, referencePosition, table, value }: EditContext<V>) {\n this.table = table;\n this.ended = false;\n this.cancelled = false;\n this.endEdit = endEdit;\n this.original = value;\n this.query = '';\n this.activeIndex = 0;\n this.selected = this.fromValue(value);\n this.initialKeys = this.selectedKeys();\n this.syncCurrent();\n this.attach(container, referencePosition.rect, styles.picker);\n this.place(referencePosition.rect);\n this.paint(this.panel());\n }\n\n adjustPosition(rect: RectProps) {\n this.place(rect);\n }\n\n /** 打开那一刻选中项的键快照 —— 用来判断用户到底动没动过。 */\n private initialKeys = '';\n\n private selectedKeys(): string {\n return this.selected\n .map((item) => this.keyOf(item))\n .sort()\n .join('\\u0000');\n }\n\n protected syncCurrent() {\n // **没动过就原样回传使用方的值。** `fromValue` → `publish` 是一次归一化往返:\n // `'a@b.com'` 会变成 `['a@b.com']`、`{ name }` 会变成 `[{ name }]`。而单击即开、\n // 点面板外即提交 —— 用户只是看了一眼再点走,照归一化结果写回去就是**静默改型**:\n // 使用方按 string 读该字段的代码从此拿到数组。inline 编辑器一直守着这条\n // (`raw === this.displayed ? this.original : raw`),picker 这边漏了。\n this.current =\n this.selectedKeys() === this.initialKeys ? this.original : this.publish(this.selected);\n }\n\n private place(rect: RectProps) {\n if (this.mount) placePicker(this.mount, rect, this.panelMinWidth(), this.tableTop());\n }\n\n private visible() {\n return this.candidates().filter((item) => this.matches(item, this.query));\n }\n\n private has(item: TItem) {\n const key = this.keyOf(item);\n return this.selected.some((entry) => this.keyOf(entry) === key);\n }\n\n private removeSelected(item: TItem) {\n const key = this.keyOf(item);\n this.selected = this.selected.filter((entry) => this.keyOf(entry) !== key);\n this.syncCurrent();\n // candidates() 可能回退成已选项(UserEditor 没传 users 时就是这样),删一个 chip 就会\n // 让候选列变短。不夹紧的话 activeIndex 越界,Enter 静默失效且没有任何行是高亮的。\n // 下界也要夹:候选清空时 `length - 1` 是 -1,留个负数会让后面每次 paint 都不高亮任何行。\n this.activeIndex = Math.max(0, Math.min(this.activeIndex, this.visible().length - 1));\n this.paint(this.panel());\n }\n\n private toggle(item: TItem) {\n if (this.multiple === false) {\n // **再点一次已选中的那一项 = 取消**。单选没有别的手势能回到空值:「清空」行与面板顶部的\n // 已选 chip 都已移除,而单元格 chip 上那颗 × 是纯鼠标的(span + onClick,不可聚焦)。\n // 走这一条路,键盘也就通了(方向键 + 回车)。\n this.selected = this.has(item) ? [] : [item];\n this.syncCurrent();\n this.finish();\n return;\n }\n\n this.selected = this.has(item)\n ? this.selected.filter((entry) => this.keyOf(entry) !== this.keyOf(item))\n : [...this.selected, item];\n this.syncCurrent();\n this.paint(this.panel());\n }\n\n private moveActive(delta: number, rowCount: number) {\n this.activeIndex = Math.min(Math.max(this.activeIndex + delta, 0), Math.max(rowCount - 1, 0));\n this.paint(this.panel());\n }\n\n private selectActive(items: TItem[]) {\n const item = items[this.activeIndex];\n if (item) this.toggle(item);\n }\n\n private activate(index: number) {\n if (this.activeIndex === index) return;\n this.activeIndex = index;\n this.paint(this.panel());\n }\n\n private panel() {\n const items = this.visible();\n const rowCount = items.length;\n\n return (\n <>\n <Input\n autoFocus\n className={styles.pickerSearch}\n placeholder={this.searchPlaceholder()}\n size=\"middle\"\n value={this.query}\n variant=\"borderless\"\n onChange={(event) => {\n this.query = event.target.value;\n this.activeIndex = 0;\n this.paint(this.panel());\n }}\n onKeyDown={(event) => {\n if (\n event.key === 'Backspace' &&\n this.query === '' &&\n this.multiple &&\n this.selected.length > 0\n ) {\n event.preventDefault();\n event.stopPropagation();\n const last = this.selected.at(-1);\n if (last) this.removeSelected(last);\n return;\n }\n if (event.key === 'ArrowDown') {\n event.preventDefault();\n event.stopPropagation();\n this.moveActive(1, rowCount);\n return;\n }\n if (event.key === 'ArrowUp') {\n event.preventDefault();\n event.stopPropagation();\n this.moveActive(-1, rowCount);\n return;\n }\n if (event.key === 'Enter') {\n event.preventDefault();\n event.stopPropagation();\n this.selectActive(items);\n return;\n }\n if (event.key === 'Escape') {\n event.preventDefault();\n event.stopPropagation();\n this.cancelled = true;\n this.finish();\n }\n }}\n />\n {items.length === 0 ? (\n <Empty\n description={tableText(this.table, 'noMatch')}\n image={Empty.PRESENTED_IMAGE_SIMPLE}\n />\n ) : null}\n {items.map((item, index) => (\n <button\n className={styles.pickerItem}\n data-active={index === this.activeIndex ? 'true' : 'false'}\n key={this.keyOf(item)}\n type=\"button\"\n onClick={() => this.toggle(item)}\n onMouseEnter={() => this.activate(index)}\n >\n {this.renderRow(item)}\n <span className={styles.check}>{this.has(item) ? '✓' : ''}</span>\n </button>\n ))}\n </>\n );\n }\n}\n\nexport const isPickerEditor = (editor: unknown): editor is TableEditor =>\n Boolean(\n editor &&\n typeof editor === 'object' &&\n 'kind' in editor &&\n (editor as TableEditor).kind === 'picker',\n );\n"],"mappings":"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"}
1
+ {"version":3,"file":"base.mjs","names":[],"sources":["../../../../src/Table/react/editors/base.tsx"],"sourcesContent":["'use client';\n\nimport {\n type EditContext,\n type IEditor,\n type RectProps,\n ValidateEnum,\n} from '@visactor/vtable-editors';\nimport { ConfigProvider, Empty, Input, type ThemeConfig } from 'antd';\nimport { type ReactNode } from 'react';\nimport { createRoot, type Root } from 'react-dom/client';\n\nimport { copyToClipboard } from '@/utils/copyToClipboard';\n\nimport { antdLocaleFor, readTableI18n, tableText } from '../../core/tableI18n';\nimport { updateCellContent, updateNextFrame } from '../../vtable/vtableAdapter';\nimport { styles } from '../style';\n\nexport type EditorKind = 'inline' | 'picker';\n\nexport interface TableEditor {\n kind: EditorKind;\n}\n\n/** 按表记录:模块级单例会让一张表关掉 picker 之后,另一张表 160ms 内的点击被误吞。 */\nconst pickerClosedAt = new WeakMap<object, number>();\n\n/**\n * 编辑器里的 Ctrl/Cmd+A 必须**留在输入框里**。\n *\n * 引擎在 `table.getElement()` 上监听 keydown(编辑器 DOM 正是它的子节点),见到这个组合就\n * `preventDefault()` + `deelTableSelectAll()`:输入框里的文本永远选不中,紧接着的输入是\n * **插到光标处**而不是替换 —— 数字列会写出用户没打算写的值,JSON 列直接判非法。引擎自带的\n * `InputEditor` 专门为它加了 `stopPropagation()`,我们的编辑器要对齐。\n *\n * 监听挂在编辑器根节点的**冒泡**阶段:事件先到输入框自己,再被这里截住,不再往上走到表元素。\n */\nexport const keepSelectAllInEditor = (root: HTMLElement): void => {\n root.addEventListener('keydown', (event) => {\n if ((event.ctrlKey || event.metaKey) && (event.key === 'a' || event.key === 'A')) {\n event.stopPropagation();\n }\n });\n};\n\nexport const markPickerClosed = (table?: unknown) => {\n if (table == null || typeof table !== 'object') return;\n pickerClosedAt.set(table as object, Date.now());\n};\n\nexport const pickerJustClosed = (table?: unknown) => {\n if (table == null || typeof table !== 'object') return false;\n const at = pickerClosedAt.get(table as object);\n return at != null && Date.now() - at < 160;\n};\n\nconst POPUP_SELECTOR = '.ant-picker-dropdown, .ant-picker-panel-container, .ant-picker-footer';\n\n/** 浮层离容器左右边缘留出的缝。贴边看起来像是被裁掉。 */\nconst EDGE_GUTTER = 8;\n\n/**\n * 水平方向把浮层收进容器:右缘放不下先向左回移,回移到头还放不下就收窄。\n *\n * `rect` 是相对**容器**(VTable 传进来的 container 就是 `table.getElement()`)的,所以夹取的\n * 右界取 `container.clientWidth`,与 `left` 同源。保的是「在容器可视宽度内展示全」,不是视口。\n *\n * **调用方必须先把浮层挂进容器**,否则这里读不到 `parentElement`,夹取被整条跳过。\n */\nconst clampHorizontal = (\n element: HTMLElement,\n rect: RectProps,\n desiredWidth: number,\n): { left: number; width: number } => {\n const containerWidth = element.parentElement?.clientWidth;\n const left = rect.left - 1;\n if (containerWidth == null || containerWidth <= 0) return { left, width: desiredWidth };\n\n // 容器比浮层还窄(窄容器 / 窄视口里的表)时只能收窄:光回移左缘也顶不住,右侧照样探出去。\n const width = Math.min(desiredWidth, Math.max(containerWidth - EDGE_GUTTER * 2, 1));\n const maxLeft = Math.max(containerWidth - width - EDGE_GUTTER, EDGE_GUTTER);\n\n return { left: Math.min(left, maxLeft), width };\n};\n\nexport const applyOverlayBox = (element: HTMLElement, rect: RectProps) => {\n const { left, width } = clampHorizontal(element, rect, Math.max(rect.width + 2, 240));\n element.style.top = `${rect.top - 1}px`;\n element.style.left = `${left}px`;\n element.style.width = `${width}px`;\n element.style.minHeight = `${Math.max(rect.height, 32)}px`;\n element.style.height = `${Math.max(rect.height, 32)}px`;\n};\n\n/**\n * `rect` 是**相对表格**的,而 `window.innerHeight` 是视口单位 —— 翻转判断必须加上表格自己在\n * 视口里的位置,否则两者不同源、算式再对也不成立。\n *\n * 曾经这里取的是 `element.parentElement` 的 top。挂载点未必就是 `rect` 的基准元素(VTable 传进来\n * 的 container 与表格元素不是同一个),实测面板超出视口 105px 却完全不翻转 —— 而单看算式\n * (`containerTop + belowTop + estimatedHeight > innerHeight`)是满足翻转条件的。\n */\n/** 面板的最大高度,翻转判断按它估高(见 `placePicker`)。**必须与 `styles.picker` 的\n * `max-height` 一致**:`placePicker` 优先读计算样式,这里是读不到时的兜底。 */\nconst PANEL_MAX_HEIGHT = 320;\n\nexport const placePicker = (element: HTMLElement, rect: RectProps, minWidth = 240, baseTop = 0) => {\n const { left, width } = clampHorizontal(element, rect, Math.max(rect.width + 2, minWidth));\n // 用面板的 max-height 估高,**不能用 `scrollHeight`**:`attach` 刚把高度设成单元格高度,\n // 而且 React 的首次渲染还没落地 —— 此刻 scrollHeight 是 36 之类的中间值(不是 0,所以连\n // `|| 240` 的兜底都救不回来),翻转判断因此算出「放得下」,而实际超出视口 105px 却完全不翻。\n const estimatedHeight =\n Number.parseFloat(getComputedStyle(element).maxHeight) || PANEL_MAX_HEIGHT;\n const belowTop = rect.top + rect.height + 4;\n let top = belowTop;\n\n if (typeof window !== 'undefined' && baseTop + belowTop + estimatedHeight > window.innerHeight) {\n top = Math.max(rect.top - estimatedHeight - 4, 0);\n }\n\n element.style.top = `${top}px`;\n element.style.left = `${left}px`;\n // min-width 跟着夹取后的宽度写:照 minWidth 原样写会在窄容器里把面板重新顶宽。\n element.style.minWidth = `${width}px`;\n element.style.width = `${width}px`;\n element.style.height = 'auto';\n element.style.maxHeight = `${PANEL_MAX_HEIGHT}px`;\n};\n\nexport const detach = (element?: HTMLElement) => {\n element?.parentElement?.removeChild(element);\n};\n\nconst COPY_ICON =\n '<svg fill=\"none\" height=\"14\" viewBox=\"0 0 16 16\" width=\"14\"><rect height=\"8\" rx=\"1.5\" stroke=\"currentColor\" width=\"8\" x=\"5\" y=\"5\"/><path d=\"M3 11V4.5A1.5 1.5 0 0 1 4.5 3H11\" stroke=\"currentColor\" stroke-linecap=\"round\"/></svg>';\n\nexport const createCopyButton = (getValue: () => string, table?: unknown) => {\n const button = document.createElement('button');\n button.type = 'button';\n button.className = styles.copyButton;\n button.setAttribute('aria-label', tableText(table, 'copy'));\n button.innerHTML = COPY_ICON;\n button.addEventListener('mousedown', (event) => event.preventDefault());\n button.addEventListener('click', (event) => {\n event.preventDefault();\n event.stopPropagation();\n void copyToClipboard(getValue());\n });\n\n return button;\n};\n\n/** 提交被拒时打在编辑器宿主上的属性,样式见 `styles.editor`。 */\nexport const INVALID_ATTR = 'data-superad-invalid';\n\n/**\n * 提交被拒时给出**可见**反馈。静默拒绝和静默改型一样糟:用户按了回车,什么都没发生。\n */\nexport const markEditorInvalid = (host?: HTMLElement): void => {\n host?.setAttribute(INVALID_ATTR, '');\n};\n\n/** 重新编辑即清掉非法态,否则用户改对了还是红的。 */\nexport const watchEditorValidity = (host: HTMLElement, input: HTMLElement): void => {\n input.addEventListener('input', () => host.removeAttribute(INVALID_ATTR));\n};\n\n/**\n * 点在编辑器**外面**就提交(inline 编辑器用,picker 走自己的 `onDocumentPointerDown`)。\n *\n * 引擎的提交手势只有三类,**全部绑在 canvas 或表元素上**:canvas 上的 pointerdown、\n * `table.getElement()` 上的 Enter/Tab/Escape/方向键。于是「打开编辑器 → 输入 → 点页面别处」\n * 这条路什么都不触发:编辑器既不提交也不关闭,用户以为已经保存。表随后被卸载/更新时走的是\n * `cancelEditCell`(不写值),输入就此**静默丢失**。\n *\n * 返回解绑函数,`onEnd` 里调用。\n */\nexport const watchOutsideCommit = (host: HTMLElement, getTable: () => unknown): (() => void) => {\n const onPointerDown = (event: PointerEvent) => {\n if (host.contains(event.target as Node)) return;\n\n (getTable() as { completeEditCell?: () => void } | undefined)?.completeEditCell?.();\n };\n\n document.addEventListener('pointerdown', onPointerDown, true);\n\n return () => document.removeEventListener('pointerdown', onPointerDown, true);\n};\n\n/**\n * 编辑器渲染在**自己的 React root** 里(`createRoot(mount)`,由 VTable 的编辑器协议驱动),\n * 而 React context 不跨 root —— 这里这个 `ConfigProvider` 拿不到外层 `ThemeProvider` 的主题,\n * 不显式传 `theme` 就会退回 antd 默认(主色 `#1677ff` 的蓝)。\n *\n * 实测:日期面板的「此刻 / 确定」按钮与选中日期都是 `rgb(22,119,255)`,而本项目的\n * `--ant-color-primary` 是 `#222222`(近黑)。同一个 root 里所有 antd 组件(Input / Empty /\n * Avatar…)都受这一条影响。主题由 `table.__superadTheme` 送进来,与 canvas 调色板走同一条通道\n * (见 `useTableRenderState`)。\n *\n * 弹层仍然 portal 到 `document.body`:挂进表格容器的话,鼠标扫过弹层会命中表格的\n * pointermove,凭空点亮 hover 悬浮框。\n */\nconst wrapEditor = (node: ReactNode, theme?: ThemeConfig, locale?: string) => (\n <ConfigProvider\n getPopupContainer={() => document.body}\n locale={antdLocaleFor(locale)}\n theme={theme}\n >\n {node}\n </ConfigProvider>\n);\n\n/**\n * 提交时**自己补写一次**。\n *\n * 引擎在「分组 + 树」的**嵌套行**上写入会失败:`_CachedDataSource` 映射不到记录,\n * `changeCellValues` **静默无操作**(`changeCellValue` 则直接抛\n * `TypeError: Cannot read properties of undefined (reading 'toString')`)。\n * 症状是「编辑完值不见了」,而且整张表和原始 records 里都找不到它 —— 触发条件是\n * **有子节点的行**,以及**嵌在它们下面的行**,平铺表完全不受影响。\n *\n * 这里按 `getCellOriginRecord` 拿到记录对象直接写;调用方再返回 `invalidateExit`,\n * 引擎会 `setValue(旧值)` + `doExit()` —— 它自己那次写入本来就会失败,不影响我们写进去\n * 的值。正常行上是同值重写,无害。\n *\n * `invalidateExit` 会调 `setValue` 回滚,值已由这里写好,所以调用方给个空实现即可。\n *\n * 注意:inline 那三个编辑器**没有继承 `ReactEditor`**(它们是独立类、只 implements 接口),\n * 所以这里是自由函数而不是基类方法 —— 只挂在基类上会漏掉它们。\n */\nexport const writeBackOnCommit = (editor: {\n getValue: () => unknown;\n table?: unknown;\n cancelled?: boolean;\n}): void => {\n const table = editor.table as\n | {\n editorManager?: { editCell?: { col: number; row: number } };\n getBodyColumnDefine?: (col: number, row: number) => { field?: string } | undefined;\n getCellOriginRecord?: (col: number, row: number) => Record<string, unknown> | undefined;\n }\n | undefined;\n const cell = table?.editorManager?.editCell;\n\n if (table == null || cell == null || editor.cancelled === true) return;\n\n const record = table.getCellOriginRecord?.(cell.col, cell.row);\n const field = table.getBodyColumnDefine?.(cell.col, cell.row)?.field;\n const value = editor.getValue();\n\n if (record != null && field != null && record[field] !== value) {\n record[field] = value;\n updateCellContent(table as never, cell.col, cell.row);\n updateNextFrame(table as never);\n }\n};\n\nexport abstract class ReactEditor<V> implements IEditor<V>, TableEditor {\n kind: EditorKind = 'inline';\n protected current!: V;\n protected original!: V;\n protected cancelled = false;\n protected ended = false;\n protected endEdit?: () => void;\n protected mount?: HTMLDivElement;\n /** picker 关闭要靠它标明是哪张表,所以 onStart 里要把它存下来。 */\n protected table?: unknown;\n private root?: Root;\n\n abstract onStart(context: EditContext<V>): void;\n\n protected attach(container: HTMLElement, rect: RectProps, className = styles.editor) {\n const mount = document.createElement('div');\n mount.className = className;\n\n // 先挂进容器再定位:applyOverlayBox / placePicker 的水平夹取要读 parentElement.clientWidth。\n container.appendChild(mount);\n applyOverlayBox(mount, rect);\n // 面板里的搜索框 / antd 日期输入框都在表元素内部,Ctrl+A 同样会被引擎吞掉(见\n // `keepSelectAllInEditor`)。\n keepSelectAllInEditor(mount);\n document.addEventListener('pointerdown', this.onDocumentPointerDown, true);\n\n this.mount = mount;\n this.root = createRoot(mount);\n }\n\n /**\n * 提交前的收尾钩子。`ArrayEditor` 用它把**还没回车**的草稿收进值里:document 的捕获\n * pointerdown 先于输入框的 blur 跑,等到 blur 再 `commitDraft` 已经晚了(编辑器已结束)。\n */\n protected beforeFinish(): void {}\n\n private onDocumentPointerDown = (event: PointerEvent) => {\n if (this.mount && this.isEditorElement(event.target as HTMLElement) === false) {\n // 点面板外 = 提交,不是取消。inline 编辑器在 VTable 那边走的是 completeEdit(提交),\n // picker 若在这里判取消,多选(tag / user 默认 multiple)就完全没有提交手势 —— 每勾\n // 一项只重绘不 finish,用户点走即整份丢弃。取消只剩 Escape。\n this.beforeFinish();\n this.finish();\n }\n };\n\n validateValue(): ValidateEnum {\n writeBackOnCommit(this as never);\n return ValidateEnum.invalidateExit;\n }\n\n /** `invalidateExit` 会调它回滚,值已由 `writeBackOnCommit` 写好 —— 空实现即可。 */\n setValue(): void {}\n\n protected paint(node: ReactNode) {\n this.root?.render(wrapEditor(node, this.antdTheme(), readTableI18n(this.table).locale));\n }\n\n /**\n * 当前主题。由 `createTableOption` 写进 `customConfig`(与 `__superadPalette` 同一条通道),\n * 主题变化时随 option 重建一起更新。\n */\n protected antdTheme(): ThemeConfig | undefined {\n return (\n this.table as\n | {\n __superadTheme?: ThemeConfig;\n options?: { customConfig?: { __superadTheme?: ThemeConfig } };\n }\n | undefined\n )?.options?.customConfig?.__superadTheme;\n }\n\n /** 表格元素在视口里的顶边 —— `rect` 是相对表格的,翻转判断要拿它换算。 */\n protected tableTop(): number {\n const element = (this.table as { getElement?: () => HTMLElement } | undefined)?.getElement?.();\n return element ? element.getBoundingClientRect().top : 0;\n }\n\n protected finish() {\n if (this.ended) return;\n this.ended = true;\n this.endEdit?.();\n }\n\n getValue() {\n return this.cancelled ? this.original : this.current;\n }\n\n onEnd() {\n if (this.kind === 'picker') markPickerClosed(this.table);\n document.removeEventListener('pointerdown', this.onDocumentPointerDown, true);\n const root = this.root;\n const mount = this.mount;\n this.root = undefined;\n this.mount = undefined;\n this.endEdit = undefined;\n // 结束编辑常发生在同一 root 的 React 事件回调里(picker 点选项、日期 Input\n // onChange)。同步 unmount 会触发 React 警告:\n // 「Attempted to synchronously unmount a root while React was already rendering」。\n // 延到当前任务之后再卸,让这次事件 / 渲染先跑完。\n queueMicrotask(() => {\n root?.unmount();\n detach(mount);\n });\n }\n\n isEditorElement(target: HTMLElement) {\n return Boolean(\n this.mount?.contains(target) ||\n (typeof target?.closest === 'function' && target.closest(POPUP_SELECTOR)),\n );\n }\n\n adjustPosition(rect: RectProps) {\n if (this.mount) applyOverlayBox(this.mount, rect);\n }\n}\n\n/**\n * Shared picker for list-backed editors (options, people, …). The panel, the search\n * box and the keyboard handling are identical across them, so they live here once and\n * each editor supplies only its item shape through the hooks below.\n */\nexport abstract class ListPickerEditor<TItem, V> extends ReactEditor<V> {\n kind: EditorKind = 'picker';\n protected activeIndex = 0;\n protected multiple: boolean;\n protected query = '';\n protected selected: TItem[] = [];\n\n constructor(multiple = false) {\n super();\n this.multiple = multiple;\n }\n\n /** Every candidate, before the search query is applied. */\n protected abstract candidates(): TItem[];\n /** The items a cell value names. */\n protected abstract fromValue(value: V): TItem[];\n /** Stable identity, used for React keys and for equality. */\n protected abstract keyOf(item: TItem): string;\n protected abstract matches(item: TItem, query: string): boolean;\n /** 候选列表里一行长什么样。已选项只靠行尾的 ✓ 标出来,面板顶部不再另起一排 chip。 */\n protected abstract renderRow(item: TItem): ReactNode;\n /** The value the cell keeps for the current selection. */\n protected abstract publish(items: TItem[]): V;\n\n /** 搜索框的占位文案。各 picker 自己说清楚搜的是什么。 */\n protected searchPlaceholder(): string {\n return tableText(this.table, 'search');\n }\n\n /**\n * 面板的最小宽度。默认 240(Notion 人员 / 选项浮层实测宽度);\n * 地址表单要更宽,因为它是两列字段。\n */\n protected panelMinWidth(): number {\n return 240;\n }\n\n onStart({ container, endEdit, referencePosition, table, value }: EditContext<V>) {\n this.table = table;\n this.ended = false;\n this.cancelled = false;\n this.endEdit = endEdit;\n this.original = value;\n this.query = '';\n this.activeIndex = 0;\n this.selected = this.fromValue(value);\n this.initialKeys = this.selectedKeys();\n this.syncCurrent();\n this.attach(container, referencePosition.rect, styles.picker);\n this.place(referencePosition.rect);\n this.paint(this.panel());\n }\n\n adjustPosition(rect: RectProps) {\n this.place(rect);\n }\n\n /** 打开那一刻选中项的键快照 —— 用来判断用户到底动没动过。 */\n private initialKeys = '';\n\n private selectedKeys(): string {\n return this.selected\n .map((item) => this.keyOf(item))\n .sort()\n .join('\\u0000');\n }\n\n protected syncCurrent() {\n // **没动过就原样回传使用方的值。** `fromValue` → `publish` 是一次归一化往返:\n // `'a@b.com'` 会变成 `['a@b.com']`、`{ name }` 会变成 `[{ name }]`。而单击即开、\n // 点面板外即提交 —— 用户只是看了一眼再点走,照归一化结果写回去就是**静默改型**:\n // 使用方按 string 读该字段的代码从此拿到数组。inline 编辑器一直守着这条\n // (`raw === this.displayed ? this.original : raw`),picker 这边漏了。\n this.current =\n this.selectedKeys() === this.initialKeys ? this.original : this.publish(this.selected);\n }\n\n private place(rect: RectProps) {\n if (this.mount) placePicker(this.mount, rect, this.panelMinWidth(), this.tableTop());\n }\n\n private visible() {\n return this.candidates().filter((item) => this.matches(item, this.query));\n }\n\n private has(item: TItem) {\n const key = this.keyOf(item);\n return this.selected.some((entry) => this.keyOf(entry) === key);\n }\n\n private removeSelected(item: TItem) {\n const key = this.keyOf(item);\n this.selected = this.selected.filter((entry) => this.keyOf(entry) !== key);\n this.syncCurrent();\n // candidates() 可能回退成已选项(UserEditor 没传 users 时就是这样),删一个 chip 就会\n // 让候选列变短。不夹紧的话 activeIndex 越界,Enter 静默失效且没有任何行是高亮的。\n // 下界也要夹:候选清空时 `length - 1` 是 -1,留个负数会让后面每次 paint 都不高亮任何行。\n this.activeIndex = Math.max(0, Math.min(this.activeIndex, this.visible().length - 1));\n this.paint(this.panel());\n }\n\n private toggle(item: TItem) {\n if (this.multiple === false) {\n // **再点一次已选中的那一项 = 取消**。单选没有别的手势能回到空值:「清空」行与面板顶部的\n // 已选 chip 都已移除,而单元格 chip 上那颗 × 是纯鼠标的(span + onClick,不可聚焦)。\n // 走这一条路,键盘也就通了(方向键 + 回车)。\n this.selected = this.has(item) ? [] : [item];\n this.syncCurrent();\n this.finish();\n return;\n }\n\n this.selected = this.has(item)\n ? this.selected.filter((entry) => this.keyOf(entry) !== this.keyOf(item))\n : [...this.selected, item];\n this.syncCurrent();\n this.paint(this.panel());\n }\n\n private moveActive(delta: number, rowCount: number) {\n this.activeIndex = Math.min(Math.max(this.activeIndex + delta, 0), Math.max(rowCount - 1, 0));\n this.paint(this.panel());\n }\n\n private selectActive(items: TItem[]) {\n const item = items[this.activeIndex];\n if (item) this.toggle(item);\n }\n\n private activate(index: number) {\n if (this.activeIndex === index) return;\n this.activeIndex = index;\n this.paint(this.panel());\n }\n\n private panel() {\n const items = this.visible();\n const rowCount = items.length;\n\n return (\n <>\n <Input\n autoFocus\n className={styles.pickerSearch}\n placeholder={this.searchPlaceholder()}\n size=\"middle\"\n value={this.query}\n variant=\"borderless\"\n onChange={(event) => {\n this.query = event.target.value;\n this.activeIndex = 0;\n this.paint(this.panel());\n }}\n onKeyDown={(event) => {\n if (\n event.key === 'Backspace' &&\n this.query === '' &&\n this.multiple &&\n this.selected.length > 0\n ) {\n event.preventDefault();\n event.stopPropagation();\n const last = this.selected.at(-1);\n if (last) this.removeSelected(last);\n return;\n }\n if (event.key === 'ArrowDown') {\n event.preventDefault();\n event.stopPropagation();\n this.moveActive(1, rowCount);\n return;\n }\n if (event.key === 'ArrowUp') {\n event.preventDefault();\n event.stopPropagation();\n this.moveActive(-1, rowCount);\n return;\n }\n if (event.key === 'Enter') {\n event.preventDefault();\n event.stopPropagation();\n this.selectActive(items);\n return;\n }\n if (event.key === 'Escape') {\n event.preventDefault();\n event.stopPropagation();\n this.cancelled = true;\n this.finish();\n }\n }}\n />\n {items.length === 0 ? (\n <Empty\n description={tableText(this.table, 'noMatch')}\n image={Empty.PRESENTED_IMAGE_SIMPLE}\n />\n ) : null}\n {items.map((item, index) => (\n <button\n className={styles.pickerItem}\n data-active={index === this.activeIndex ? 'true' : 'false'}\n key={this.keyOf(item)}\n type=\"button\"\n onClick={() => this.toggle(item)}\n onMouseEnter={() => this.activate(index)}\n >\n {this.renderRow(item)}\n <span className={styles.check}>{this.has(item) ? '✓' : ''}</span>\n </button>\n ))}\n </>\n );\n }\n}\n\nexport const isPickerEditor = (editor: unknown): editor is TableEditor =>\n Boolean(\n editor &&\n typeof editor === 'object' &&\n 'kind' in editor &&\n (editor as TableEditor).kind === 'picker',\n );\n"],"mappings":";;;;;;;;;;;AAyBA,MAAM,iCAAiB,IAAI,QAAwB;;;;;;;;;;;AAYnD,MAAa,yBAAyB,SAA4B;CAChE,KAAK,iBAAiB,YAAY,UAAU;EAC1C,KAAK,MAAM,WAAW,MAAM,aAAa,MAAM,QAAQ,OAAO,MAAM,QAAQ,MAC1E,MAAM,gBAAgB;CAE1B,CAAC;AACH;AAEA,MAAa,oBAAoB,UAAoB;CACnD,IAAI,SAAS,QAAQ,OAAO,UAAU,UAAU;CAChD,eAAe,IAAI,OAAiB,KAAK,IAAI,CAAC;AAChD;AAEA,MAAa,oBAAoB,UAAoB;CACnD,IAAI,SAAS,QAAQ,OAAO,UAAU,UAAU,OAAO;CACvD,MAAM,KAAK,eAAe,IAAI,KAAe;CAC7C,OAAO,MAAM,QAAQ,KAAK,IAAI,IAAI,KAAK;AACzC;AAEA,MAAM,iBAAiB;;AAGvB,MAAM,cAAc;;;;;;;;;AAUpB,MAAM,mBACJ,SACA,MACA,iBACoC;CACpC,MAAM,iBAAiB,QAAQ,eAAe;CAC9C,MAAM,OAAO,KAAK,OAAO;CACzB,IAAI,kBAAkB,QAAQ,kBAAkB,GAAG,OAAO;EAAE;EAAM,OAAO;CAAa;CAGtF,MAAM,QAAQ,KAAK,IAAI,cAAc,KAAK,IAAI,iBAAiB,IAAiB,CAAC,CAAC;CAClF,MAAM,UAAU,KAAK,IAAI,iBAAiB,QAAQ,aAAa,WAAW;CAE1E,OAAO;EAAE,MAAM,KAAK,IAAI,MAAM,OAAO;EAAG;CAAM;AAChD;AAEA,MAAa,mBAAmB,SAAsB,SAAoB;CACxE,MAAM,EAAE,MAAM,UAAU,gBAAgB,SAAS,MAAM,KAAK,IAAI,KAAK,QAAQ,GAAG,GAAG,CAAC;CACpF,QAAQ,MAAM,MAAM,GAAG,KAAK,MAAM,EAAE;CACpC,QAAQ,MAAM,OAAO,GAAG,KAAK;CAC7B,QAAQ,MAAM,QAAQ,GAAG,MAAM;CAC/B,QAAQ,MAAM,YAAY,GAAG,KAAK,IAAI,KAAK,QAAQ,EAAE,EAAE;CACvD,QAAQ,MAAM,SAAS,GAAG,KAAK,IAAI,KAAK,QAAQ,EAAE,EAAE;AACtD;;;;;;;;;;;AAYA,MAAM,mBAAmB;AAEzB,MAAa,eAAe,SAAsB,MAAiB,WAAW,KAAK,UAAU,MAAM;CACjG,MAAM,EAAE,MAAM,UAAU,gBAAgB,SAAS,MAAM,KAAK,IAAI,KAAK,QAAQ,GAAG,QAAQ,CAAC;CAIzF,MAAM,kBACJ,OAAO,WAAW,iBAAiB,OAAO,CAAC,CAAC,SAAS,KAAK;CAC5D,MAAM,WAAW,KAAK,MAAM,KAAK,SAAS;CAC1C,IAAI,MAAM;CAEV,IAAI,OAAO,WAAW,eAAe,UAAU,WAAW,kBAAkB,OAAO,aACjF,MAAM,KAAK,IAAI,KAAK,MAAM,kBAAkB,GAAG,CAAC;CAGlD,QAAQ,MAAM,MAAM,GAAG,IAAI;CAC3B,QAAQ,MAAM,OAAO,GAAG,KAAK;CAE7B,QAAQ,MAAM,WAAW,GAAG,MAAM;CAClC,QAAQ,MAAM,QAAQ,GAAG,MAAM;CAC/B,QAAQ,MAAM,SAAS;CACvB,QAAQ,MAAM,YAAY,GAAG,iBAAiB;AAChD;AAEA,MAAa,UAAU,YAA0B;CAC/C,SAAS,eAAe,YAAY,OAAO;AAC7C;AAEA,MAAM,YACJ;AAEF,MAAa,oBAAoB,UAAwB,UAAoB;CAC3E,MAAM,SAAS,SAAS,cAAc,QAAQ;CAC9C,OAAO,OAAO;CACd,OAAO,YAAY,OAAO;CAC1B,OAAO,aAAa,cAAc,UAAU,OAAO,MAAM,CAAC;CAC1D,OAAO,YAAY;CACnB,OAAO,iBAAiB,cAAc,UAAU,MAAM,eAAe,CAAC;CACtE,OAAO,iBAAiB,UAAU,UAAU;EAC1C,MAAM,eAAe;EACrB,MAAM,gBAAgB;EACtB,gBAAqB,SAAS,CAAC;CACjC,CAAC;CAED,OAAO;AACT;;AAGA,MAAa,eAAe;;;;AAK5B,MAAa,qBAAqB,SAA6B;CAC7D,MAAM,aAAa,cAAc,EAAE;AACrC;;AAGA,MAAa,uBAAuB,MAAmB,UAA6B;CAClF,MAAM,iBAAiB,eAAe,KAAK,gBAAgB,YAAY,CAAC;AAC1E;;;;;;;;;;;AAYA,MAAa,sBAAsB,MAAmB,aAA0C;CAC9F,MAAM,iBAAiB,UAAwB;EAC7C,IAAI,KAAK,SAAS,MAAM,MAAc,GAAG;EAEzC,SAAU,CAAC,EAAoD,mBAAmB;CACpF;CAEA,SAAS,iBAAiB,eAAe,eAAe,IAAI;CAE5D,aAAa,SAAS,oBAAoB,eAAe,eAAe,IAAI;AAC9E;;;;;;;;;;;;;;AAeA,MAAM,cAAc,MAAiB,OAAqB,WACxD,oBAAC,gBAAD;CACE,yBAAyB,SAAS;CAClC,QAAQ,cAAc,MAAM;CACrB;CAEN,UAAA;AACa,CAAA;;;;;;;;;;;;;;;;;;;AAqBlB,MAAa,qBAAqB,WAItB;CACV,MAAM,QAAQ,OAAO;CAOrB,MAAM,OAAO,OAAO,eAAe;CAEnC,IAAI,SAAS,QAAQ,QAAQ,QAAQ,OAAO,cAAc,MAAM;CAEhE,MAAM,SAAS,MAAM,sBAAsB,KAAK,KAAK,KAAK,GAAG;CAC7D,MAAM,QAAQ,MAAM,sBAAsB,KAAK,KAAK,KAAK,GAAG,CAAC,EAAE;CAC/D,MAAM,QAAQ,OAAO,SAAS;CAE9B,IAAI,UAAU,QAAQ,SAAS,QAAQ,OAAO,WAAW,OAAO;EAC9D,OAAO,SAAS;EAChB,kBAAkB,OAAgB,KAAK,KAAK,KAAK,GAAG;EACpD,gBAAgB,KAAc;CAChC;AACF;AAEA,IAAsB,cAAtB,MAAwE;;EACnD,KAAA,OAAA;EAGG,KAAA,YAAA;EACJ,KAAA,QAAA;EA+Be,KAAA,yBAAA,UAAwB;GACvD,IAAI,KAAK,SAAS,KAAK,gBAAgB,MAAM,MAAqB,MAAM,OAAO;IAI7E,KAAK,aAAa;IAClB,KAAK,OAAO;GACd;EACF;;CA9BA,OAAiB,WAAwB,MAAiB,YAAY,OAAO,QAAQ;EACnF,MAAM,QAAQ,SAAS,cAAc,KAAK;EAC1C,MAAM,YAAY;EAGlB,UAAU,YAAY,KAAK;EAC3B,gBAAgB,OAAO,IAAI;EAG3B,sBAAsB,KAAK;EAC3B,SAAS,iBAAiB,eAAe,KAAK,uBAAuB,IAAI;EAEzE,KAAK,QAAQ;EACb,KAAK,OAAO,WAAW,KAAK;CAC9B;;;;;CAMA,eAA+B,CAAC;CAYhC,gBAA8B;EAC5B,kBAAkB,IAAa;EAC/B,OAAO,aAAa;CACtB;;CAGA,WAAiB,CAAC;CAElB,MAAgB,MAAiB;EAC/B,KAAK,MAAM,OAAO,WAAW,MAAM,KAAK,UAAU,GAAG,cAAc,KAAK,KAAK,CAAC,CAAC,MAAM,CAAC;CACxF;;;;;CAMA,YAA+C;EAC7C,OACE,KAAK,OAMJ,SAAS,cAAc;CAC5B;;CAGA,WAA6B;EAC3B,MAAM,UAAW,KAAK,OAA0D,aAAa;EAC7F,OAAO,UAAU,QAAQ,sBAAsB,CAAC,CAAC,MAAM;CACzD;CAEA,SAAmB;EACjB,IAAI,KAAK,OAAO;EAChB,KAAK,QAAQ;EACb,KAAK,UAAU;CACjB;CAEA,WAAW;EACT,OAAO,KAAK,YAAY,KAAK,WAAW,KAAK;CAC/C;CAEA,QAAQ;EACN,IAAI,KAAK,SAAS,UAAU,iBAAiB,KAAK,KAAK;EACvD,SAAS,oBAAoB,eAAe,KAAK,uBAAuB,IAAI;EAC5E,MAAM,OAAO,KAAK;EAClB,MAAM,QAAQ,KAAK;EACnB,KAAK,OAAO,KAAA;EACZ,KAAK,QAAQ,KAAA;EACb,KAAK,UAAU,KAAA;EAKf,qBAAqB;GACnB,MAAM,QAAQ;GACd,OAAO,KAAK;EACd,CAAC;CACH;CAEA,gBAAgB,QAAqB;EACnC,OAAO,QACL,KAAK,OAAO,SAAS,MAAM,KAC1B,OAAO,QAAQ,YAAY,cAAc,OAAO,QAAQ,cAAc,CACzE;CACF;CAEA,eAAe,MAAiB;EAC9B,IAAI,KAAK,OAAO,gBAAgB,KAAK,OAAO,IAAI;CAClD;AACF;;;;;;AAOA,IAAsB,mBAAtB,cAAyD,YAAe;CAOtE,YAAY,WAAW,OAAO;EAC5B,MAAM;EAPW,KAAA,OAAA;EACK,KAAA,cAAA;EAEN,KAAA,QAAA;EACY,KAAA,WAAA,CAAC;EAqDT,KAAA,cAAA;EAjDpB,KAAK,WAAW;CAClB;;CAeA,oBAAsC;EACpC,OAAO,UAAU,KAAK,OAAO,QAAQ;CACvC;;;;;CAMA,gBAAkC;EAChC,OAAO;CACT;CAEA,QAAQ,EAAE,WAAW,SAAS,mBAAmB,OAAO,SAAyB;EAC/E,KAAK,QAAQ;EACb,KAAK,QAAQ;EACb,KAAK,YAAY;EACjB,KAAK,UAAU;EACf,KAAK,WAAW;EAChB,KAAK,QAAQ;EACb,KAAK,cAAc;EACnB,KAAK,WAAW,KAAK,UAAU,KAAK;EACpC,KAAK,cAAc,KAAK,aAAa;EACrC,KAAK,YAAY;EACjB,KAAK,OAAO,WAAW,kBAAkB,MAAM,OAAO,MAAM;EAC5D,KAAK,MAAM,kBAAkB,IAAI;EACjC,KAAK,MAAM,KAAK,MAAM,CAAC;CACzB;CAEA,eAAe,MAAiB;EAC9B,KAAK,MAAM,IAAI;CACjB;CAKA,eAA+B;EAC7B,OAAO,KAAK,SACT,KAAK,SAAS,KAAK,MAAM,IAAI,CAAC,CAAC,CAC/B,KAAK,CAAC,CACN,KAAK,IAAQ;CAClB;CAEA,cAAwB;EAMtB,KAAK,UACH,KAAK,aAAa,MAAM,KAAK,cAAc,KAAK,WAAW,KAAK,QAAQ,KAAK,QAAQ;CACzF;CAEA,MAAc,MAAiB;EAC7B,IAAI,KAAK,OAAO,YAAY,KAAK,OAAO,MAAM,KAAK,cAAc,GAAG,KAAK,SAAS,CAAC;CACrF;CAEA,UAAkB;EAChB,OAAO,KAAK,WAAW,CAAC,CAAC,QAAQ,SAAS,KAAK,QAAQ,MAAM,KAAK,KAAK,CAAC;CAC1E;CAEA,IAAY,MAAa;EACvB,MAAM,MAAM,KAAK,MAAM,IAAI;EAC3B,OAAO,KAAK,SAAS,MAAM,UAAU,KAAK,MAAM,KAAK,MAAM,GAAG;CAChE;CAEA,eAAuB,MAAa;EAClC,MAAM,MAAM,KAAK,MAAM,IAAI;EAC3B,KAAK,WAAW,KAAK,SAAS,QAAQ,UAAU,KAAK,MAAM,KAAK,MAAM,GAAG;EACzE,KAAK,YAAY;EAIjB,KAAK,cAAc,KAAK,IAAI,GAAG,KAAK,IAAI,KAAK,aAAa,KAAK,QAAQ,CAAC,CAAC,SAAS,CAAC,CAAC;EACpF,KAAK,MAAM,KAAK,MAAM,CAAC;CACzB;CAEA,OAAe,MAAa;EAC1B,IAAI,KAAK,aAAa,OAAO;GAI3B,KAAK,WAAW,KAAK,IAAI,IAAI,IAAI,CAAC,IAAI,CAAC,IAAI;GAC3C,KAAK,YAAY;GACjB,KAAK,OAAO;GACZ;EACF;EAEA,KAAK,WAAW,KAAK,IAAI,IAAI,IACzB,KAAK,SAAS,QAAQ,UAAU,KAAK,MAAM,KAAK,MAAM,KAAK,MAAM,IAAI,CAAC,IACtE,CAAC,GAAG,KAAK,UAAU,IAAI;EAC3B,KAAK,YAAY;EACjB,KAAK,MAAM,KAAK,MAAM,CAAC;CACzB;CAEA,WAAmB,OAAe,UAAkB;EAClD,KAAK,cAAc,KAAK,IAAI,KAAK,IAAI,KAAK,cAAc,OAAO,CAAC,GAAG,KAAK,IAAI,WAAW,GAAG,CAAC,CAAC;EAC5F,KAAK,MAAM,KAAK,MAAM,CAAC;CACzB;CAEA,aAAqB,OAAgB;EACnC,MAAM,OAAO,MAAM,KAAK;EACxB,IAAI,MAAM,KAAK,OAAO,IAAI;CAC5B;CAEA,SAAiB,OAAe;EAC9B,IAAI,KAAK,gBAAgB,OAAO;EAChC,KAAK,cAAc;EACnB,KAAK,MAAM,KAAK,MAAM,CAAC;CACzB;CAEA,QAAgB;EACd,MAAM,QAAQ,KAAK,QAAQ;EAC3B,MAAM,WAAW,MAAM;EAEvB,OACE,qBAAA,UAAA,EAAA,UAAA;GACE,oBAAC,OAAD;IACE,WAAA;IACA,WAAW,OAAO;IAClB,aAAa,KAAK,kBAAkB;IACpC,MAAK;IACL,OAAO,KAAK;IACZ,SAAQ;IACR,WAAW,UAAU;KACnB,KAAK,QAAQ,MAAM,OAAO;KAC1B,KAAK,cAAc;KACnB,KAAK,MAAM,KAAK,MAAM,CAAC;IACzB;IACA,YAAY,UAAU;KACpB,IACE,MAAM,QAAQ,eACd,KAAK,UAAU,MACf,KAAK,YACL,KAAK,SAAS,SAAS,GACvB;MACA,MAAM,eAAe;MACrB,MAAM,gBAAgB;MACtB,MAAM,OAAO,KAAK,SAAS,GAAG,EAAE;MAChC,IAAI,MAAM,KAAK,eAAe,IAAI;MAClC;KACF;KACA,IAAI,MAAM,QAAQ,aAAa;MAC7B,MAAM,eAAe;MACrB,MAAM,gBAAgB;MACtB,KAAK,WAAW,GAAG,QAAQ;MAC3B;KACF;KACA,IAAI,MAAM,QAAQ,WAAW;MAC3B,MAAM,eAAe;MACrB,MAAM,gBAAgB;MACtB,KAAK,WAAW,IAAI,QAAQ;MAC5B;KACF;KACA,IAAI,MAAM,QAAQ,SAAS;MACzB,MAAM,eAAe;MACrB,MAAM,gBAAgB;MACtB,KAAK,aAAa,KAAK;MACvB;KACF;KACA,IAAI,MAAM,QAAQ,UAAU;MAC1B,MAAM,eAAe;MACrB,MAAM,gBAAgB;MACtB,KAAK,YAAY;MACjB,KAAK,OAAO;KACd;IACF;GACD,CAAA;GACA,MAAM,WAAW,IAChB,oBAAC,OAAD;IACE,aAAa,UAAU,KAAK,OAAO,SAAS;IAC5C,OAAO,MAAM;GACd,CAAA,IACC;GACH,MAAM,KAAK,MAAM,UAChB,qBAAC,UAAD;IACE,WAAW,OAAO;IAClB,eAAa,UAAU,KAAK,cAAc,SAAS;IAEnD,MAAK;IACL,eAAe,KAAK,OAAO,IAAI;IAC/B,oBAAoB,KAAK,SAAS,KAAK;IANzC,UAAA,CAQG,KAAK,UAAU,IAAI,GACpB,oBAAC,QAAD;KAAM,WAAW,OAAO;KAAQ,UAAA,KAAK,IAAI,IAAI,IAAI,MAAM;IAAS,CAAA,CAC1D;GAPD,GAAA,KAAK,MAAM,IAAI,CAOd,CACT;EACD,EAAA,CAAA;CAEN;AACF;AAEA,MAAa,kBAAkB,WAC7B,QACE,UACA,OAAO,WAAW,YAClB,UAAU,UACT,OAAuB,SAAS,QACnC"}