uicore-ts 1.1.307 → 1.1.308
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.
|
@@ -99,7 +99,7 @@ const _UITooltip = class {
|
|
|
99
99
|
const wrappedHeight = label.scrollHeight;
|
|
100
100
|
label.style.maxWidth = `${this.maxWidth}px`;
|
|
101
101
|
label.style.width = "fit-content";
|
|
102
|
-
const naturalWidth = label.
|
|
102
|
+
const naturalWidth = label.scrollWidth;
|
|
103
103
|
if (wasDetached) {
|
|
104
104
|
document.body.removeChild(label);
|
|
105
105
|
}
|
|
@@ -138,7 +138,7 @@ const _UITooltip = class {
|
|
|
138
138
|
return;
|
|
139
139
|
}
|
|
140
140
|
this._isInitialized = true;
|
|
141
|
-
|
|
141
|
+
document.body.appendChild(this.contentView.viewHTMLElement);
|
|
142
142
|
window.addEventListener("mousemove", (event) => {
|
|
143
143
|
this._mouseX = event.clientX;
|
|
144
144
|
this._mouseY = event.clientY;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../scripts/UITooltip.ts"],
|
|
4
|
-
"sourcesContent": ["import { UIColor } from \"./UIColor\"\nimport { UICore } from \"./UICore\"\nimport { UIView } from \"./UIView\"\n\n\n/**\n * UITooltip\n *\n * Framework-level mouse-following tooltip singleton.\n *\n * The default implementation uses a plain HTML element for its content \u2014\n * no UIView layout system involved. This keeps the base tooltip simple and\n * allocation-free.\n *\n * To use a UIView-based content view instead (e.g. for complex layouts),\n * override createContentView() to return a UIView subclass. The sizing and\n * positioning logic in calculateAndSetViewFrame works identically for both\n * cases since it only calls intrinsicContentHeight/Width on the content view.\n *\n * Subclass to provide app-level styling:\n * - Override createContentView() to return a custom UIView (optional)\n * - Override applyStyles() to style the default plain-HTML content\n * - Set UITooltip.sharedInstance to your instance at app startup\n *\n * Usage:\n * UITooltip.sharedInstance.attach(someView, \"Tooltip text\")\n * UITooltip.sharedInstance.detach(someView)\n */\nexport class UITooltip {\n \n // \u2500\u2500 Singleton \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n \n static sharedInstance: UITooltip = new UITooltip()\n \n // \u2500\u2500 Positioning config \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n \n offsetX: number = 14\n offsetY: number = 20\n maxWidth: number = 320\n \n // \u2500\u2500 Content view \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n \n /**\n * The view that is measured and positioned by calculateAndSetViewFrame.\n * In the default implementation this is a lightweight UIView wrapper around\n * a plain HTML label element. Subclasses may replace it with any UIView.\n */\n readonly contentView: UIView\n \n // \u2500\u2500 Private state \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n \n private _isVisible: boolean = false\n private _mouseX: number = 0\n private _mouseY: number = 0\n private _isInitialized: boolean = false\n \n /** The plain HTML label element used by the default implementation. */\n protected _labelElement?: HTMLElement\n \n private _attachedHandlers = new WeakMap<UIView, {\n enter: (sender: UIView, event: Event) => void\n leave: (sender: UIView, event: Event) => void\n }>()\n \n // \u2500\u2500 Constructor \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n \n constructor() {\n const { contentView, labelElement } = this.createContentView()\n this.contentView = contentView\n this._labelElement = labelElement\n \n const element = this.contentView.viewHTMLElement\n element.style.position = \"fixed\"\n element.style.pointerEvents = \"none\"\n element.style.display = \"none\"\n \n this.contentView.calculateAndSetViewFrame = () => {\n this._recalculateFrame()\n }\n \n this.applyStyles()\n }\n \n // \u2500\u2500 Override points \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n \n /**\n * Creates the content view and optionally a plain HTML label element.\n *\n * Default: returns a UIView containing a single <span> whose text is set\n * directly via innerHTML. The label element is returned so that setText()\n * can update it without going through the UIView system.\n *\n * Override to return a UIView-layout-based content view. In that case,\n * return labelElement: undefined \u2014 setText() will call the UIView's own\n * text-setting mechanism instead (override setText() too if needed).\n */\n protected createContentView(): { contentView: UIView; labelElement?: HTMLElement } {\n const labelElement = document.createElement(\"span\")\n labelElement.style.display = \"block\"\n labelElement.style.whiteSpace = \"pre-wrap\"\n labelElement.style.wordBreak = \"break-word\"\n labelElement.style.lineHeight = \"1.4\"\n labelElement.style.color = \"#ffffff\"\n labelElement.style.fontSize = \"12px\"\n labelElement.style.fontWeight = \"400\"\n // Padding is applied to the label element so it sits inset from the\n // container edges. The measurement in _measureHTMLLabelSize accounts\n // for this by adding padding * 2 to the measured width and height.\n const paddingPx = `${(UICore.main?.paddingLength ?? 16) * 0.5}px`\n labelElement.style.padding = paddingPx\n labelElement.style.boxSizing = \"border-box\"\n \n const contentView = new UIView()\n contentView.configureWithObject({\n backgroundColor: new UIColor(\"rgba(30, 30, 40, 0.96)\"),\n style: {\n borderRadius: \"5px\",\n boxShadow: \"0 4px 12px rgba(0,0,0,0.25)\"\n }\n })\n contentView.viewHTMLElement.appendChild(labelElement)\n \n return { contentView, labelElement }\n }\n \n /**\n * Apply styles to the default plain-HTML content view.\n * Only called when using the default createContentView() implementation.\n * Override alongside createContentView() if you provide a UIView-based\n * content view that handles its own styling.\n */\n protected applyStyles() {}\n \n /**\n * Sets the tooltip text. Override if using a UIView-based content view\n * that exposes its own text-setting API.\n */\n setText(text: string) {\n if (this._labelElement) {\n this._labelElement.textContent = text\n }\n }\n \n // \u2500\u2500 Sizing \u2014 two-pass \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n \n /**\n * Returns the display size for the content view given the current text.\n *\n * For UIView-based content: delegates to intrinsicContentHeight/Width.\n * For plain-HTML content: measures the label element directly.\n *\n * Two passes:\n * 1. Height at maxWidth \u2014 determines how the text wraps\n * 2. Width at that height \u2014 finds the minimum width that fits the\n * wrapped text, so short text doesn't leave empty space on the right\n */\n protected measureContentSize(): { width: number; height: number } {\n if (this._labelElement) {\n return this._measureHTMLLabelSize()\n }\n const height = this.contentView.intrinsicContentHeight(this.maxWidth)\n const width = Math.min(this.contentView.intrinsicContentWidth(height), this.maxWidth)\n return { width, height }\n }\n \n private _measureHTMLLabelSize(): { width: number; height: number } {\n const label = this._labelElement!\n const prevMaxWidth = label.style.maxWidth\n const prevWidth = label.style.width\n \n // Force into DOM briefly if not connected\n let wasDetached = false\n if (!label.isConnected) {\n document.body.appendChild(label)\n wasDetached = true\n }\n \n // Pass 1: constrain to maxWidth to get the wrapped height.\n label.style.maxWidth = `${this.maxWidth}px`\n label.style.width = \"\"\n const wrappedHeight = label.scrollHeight\n \n // Pass 2: set both width:fit-content and max-width:innerMax together.\n // The browser reports the minimum width that fits the content without\n // exceeding the same constraint used in pass 1 \u2014 giving us the width\n // of the longest wrapped line rather than the full single-line width.\n label.style.maxWidth = `${this.maxWidth}px`\n label.style.width = \"fit-content\"\n const naturalWidth = label.getBoundingClientRect().width\n \n if (wasDetached) {\n document.body.removeChild(label)\n }\n \n label.style.maxWidth = prevMaxWidth\n label.style.width = prevWidth\n \n // The label has CSS padding applied (box-sizing: border-box), so\n // scrollHeight and getBoundingClientRect already include it.\n return {\n width: Math.min(Math.ceil(naturalWidth), this.maxWidth),\n height: wrappedHeight\n }\n }\n \n private get core() {\n return UICore.main\n }\n \n // \u2500\u2500 Frame calculation \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n \n private _recalculateFrame() {\n const { width, height } = this.measureContentSize()\n \n const viewportWidth = window.innerWidth\n const viewportHeight = window.innerHeight\n const margin = 8\n \n let left = this._mouseX + this.offsetX\n let top = this._mouseY + this.offsetY\n \n if (left + width > viewportWidth - margin) {\n left = this._mouseX - width - this.offsetX\n }\n if (top + height > viewportHeight - margin) {\n top = this._mouseY - height - this.offsetY * 0.5\n }\n \n left = Math.max(margin, left)\n top = Math.max(margin, top)\n \n this.contentView.setFrame(\n this.contentView.frame\n .rectangleWithX(Math.round(left))\n .rectangleWithY(Math.round(top))\n .rectangleWithWidth(width)\n .rectangleWithHeight(height),\n 99999\n )\n }\n \n // \u2500\u2500 Init \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n \n private _ensureInitialized() {\n if (this._isInitialized) {\n return\n }\n this._isInitialized = true\n \n this.contentView.addedAsSubviewToView(UICore.main.rootViewController.view)\n \n window.addEventListener(\"mousemove\", (event: MouseEvent) => {\n this._mouseX = event.clientX\n this._mouseY = event.clientY\n if (this._isVisible) {\n this.contentView.calculateAndSetViewFrame()\n }\n }, { passive: true })\n }\n \n // \u2500\u2500 Public API \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n \n show(text: string) {\n this._ensureInitialized()\n this.setText(text)\n this.contentView.viewHTMLElement.style.display = \"block\"\n if (!this._labelElement) {\n // UIView-based content needs a layout pass before measuring\n this.contentView.setNeedsLayout()\n UIView.layoutViewsIfNeeded()\n }\n this._isVisible = true\n this.contentView.calculateAndSetViewFrame()\n }\n \n hide() {\n this._isVisible = false\n this.contentView.viewHTMLElement.style.display = \"none\"\n }\n \n // \u2500\u2500 Attach / detach \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n \n attach(view: UIView, text: string) {\n this.detach(view)\n \n const enterHandler = (_sender: UIView, _event: Event) => {\n this.show(text)\n }\n const leaveHandler = (_sender: UIView, _event: Event) => {\n this.hide()\n }\n \n view.controlEventTargetAccumulator.PointerHover = enterHandler\n view.controlEventTargetAccumulator.PointerLeave = leaveHandler\n \n this._attachedHandlers.set(view, { enter: enterHandler, leave: leaveHandler })\n }\n \n detach(view: UIView) {\n const handlers = this._attachedHandlers.get(view)\n if (!handlers) {\n return\n }\n \n view.removeTargetForControlEvent(UIView.controlEvent.PointerHover, handlers.enter)\n view.removeTargetForControlEvent(UIView.controlEvent.PointerLeave, handlers.leave)\n \n this._attachedHandlers.delete(view)\n \n if (this._isVisible) {\n this.hide()\n }\n }\n \n}\n"],
|
|
5
|
-
"mappings": ";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,qBAAwB;AACxB,oBAAuB;AACvB,oBAAuB;AA0BhB,MAAM,aAAN,MAAgB;AAAA,EAsCnB,cAAc;AA9Bd,mBAAkB;AAClB,mBAAkB;AAClB,oBAAmB;AAanB,SAAQ,aAAsB;AAC9B,SAAQ,UAAkB;AAC1B,SAAQ,UAAkB;AAC1B,SAAQ,iBAA0B;AAKlC,SAAQ,oBAAoB,oBAAI,QAG7B;AAKC,UAAM,EAAE,aAAa,aAAa,IAAI,KAAK,kBAAkB;AAC7D,SAAK,cAAc;AACnB,SAAK,gBAAgB;AAErB,UAAM,UAAU,KAAK,YAAY;AACjC,YAAQ,MAAM,WAAW;AACzB,YAAQ,MAAM,gBAAgB;AAC9B,YAAQ,MAAM,UAAU;AAExB,SAAK,YAAY,2BAA2B,MAAM;AAC9C,WAAK,kBAAkB;AAAA,IAC3B;AAEA,SAAK,YAAY;AAAA,EACrB;AAAA,EAeU,oBAAyE;AAhGvF;AAiGQ,UAAM,eAAe,SAAS,cAAc,MAAM;AAClD,iBAAa,MAAM,UAAU;AAC7B,iBAAa,MAAM,aAAa;AAChC,iBAAa,MAAM,YAAY;AAC/B,iBAAa,MAAM,aAAa;AAChC,iBAAa,MAAM,QAAQ;AAC3B,iBAAa,MAAM,WAAW;AAC9B,iBAAa,MAAM,aAAa;AAIhC,UAAM,YAAY,KAAI,gCAAO,SAAP,mBAAa,kBAAb,YAA8B,MAAM;AAC1D,iBAAa,MAAM,UAAU;AAC7B,iBAAa,MAAM,YAAY;AAE/B,UAAM,cAAc,IAAI,qBAAO;AAC/B,gBAAY,oBAAoB;AAAA,MAC5B,iBAAiB,IAAI,uBAAQ,wBAAwB;AAAA,MACrD,OAAO;AAAA,QACH,cAAc;AAAA,QACd,WAAW;AAAA,MACf;AAAA,IACJ,CAAC;AACD,gBAAY,gBAAgB,YAAY,YAAY;AAEpD,WAAO,EAAE,aAAa,aAAa;AAAA,EACvC;AAAA,EAQU,cAAc;AAAA,EAAC;AAAA,EAMzB,QAAQ,MAAc;AAClB,QAAI,KAAK,eAAe;AACpB,WAAK,cAAc,cAAc;AAAA,IACrC;AAAA,EACJ;AAAA,EAeU,qBAAwD;AAC9D,QAAI,KAAK,eAAe;AACpB,aAAO,KAAK,sBAAsB;AAAA,IACtC;AACA,UAAM,SAAS,KAAK,YAAY,uBAAuB,KAAK,QAAQ;AACpE,UAAM,QAAQ,KAAK,IAAI,KAAK,YAAY,sBAAsB,MAAM,GAAG,KAAK,QAAQ;AACpF,WAAO,EAAE,OAAO,OAAO;AAAA,EAC3B;AAAA,EAEQ,wBAA2D;AAC/D,UAAM,QAAQ,KAAK;AACnB,UAAM,eAAe,MAAM,MAAM;AACjC,UAAM,YAAY,MAAM,MAAM;AAG9B,QAAI,cAAc;AAClB,QAAI,CAAC,MAAM,aAAa;AACpB,eAAS,KAAK,YAAY,KAAK;AAC/B,oBAAc;AAAA,IAClB;AAGA,UAAM,MAAM,WAAW,GAAG,KAAK;AAC/B,UAAM,MAAM,QAAQ;AACpB,UAAM,gBAAgB,MAAM;
|
|
4
|
+
"sourcesContent": ["import { UIColor } from \"./UIColor\"\nimport { UICore } from \"./UICore\"\nimport { UIView } from \"./UIView\"\n\n\n/**\n * UITooltip\n *\n * Framework-level mouse-following tooltip singleton.\n *\n * The default implementation uses a plain HTML element for its content \u2014\n * no UIView layout system involved. This keeps the base tooltip simple and\n * allocation-free.\n *\n * To use a UIView-based content view instead (e.g. for complex layouts),\n * override createContentView() to return a UIView subclass. The sizing and\n * positioning logic in calculateAndSetViewFrame works identically for both\n * cases since it only calls intrinsicContentHeight/Width on the content view.\n *\n * Subclass to provide app-level styling:\n * - Override createContentView() to return a custom UIView (optional)\n * - Override applyStyles() to style the default plain-HTML content\n * - Set UITooltip.sharedInstance to your instance at app startup\n *\n * Usage:\n * UITooltip.sharedInstance.attach(someView, \"Tooltip text\")\n * UITooltip.sharedInstance.detach(someView)\n */\nexport class UITooltip {\n \n // \u2500\u2500 Singleton \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n \n static sharedInstance: UITooltip = new UITooltip()\n \n // \u2500\u2500 Positioning config \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n \n offsetX: number = 14\n offsetY: number = 20\n maxWidth: number = 320\n \n // \u2500\u2500 Content view \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n \n /**\n * The view that is measured and positioned by calculateAndSetViewFrame.\n * In the default implementation this is a lightweight UIView wrapper around\n * a plain HTML label element. Subclasses may replace it with any UIView.\n */\n readonly contentView: UIView\n \n // \u2500\u2500 Private state \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n \n private _isVisible: boolean = false\n private _mouseX: number = 0\n private _mouseY: number = 0\n private _isInitialized: boolean = false\n \n /** The plain HTML label element used by the default implementation. */\n protected _labelElement?: HTMLElement\n \n private _attachedHandlers = new WeakMap<UIView, {\n enter: (sender: UIView, event: Event) => void\n leave: (sender: UIView, event: Event) => void\n }>()\n \n // \u2500\u2500 Constructor \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n \n constructor() {\n const { contentView, labelElement } = this.createContentView()\n this.contentView = contentView\n this._labelElement = labelElement\n \n const element = this.contentView.viewHTMLElement\n element.style.position = \"fixed\"\n element.style.pointerEvents = \"none\"\n element.style.display = \"none\"\n \n this.contentView.calculateAndSetViewFrame = () => {\n this._recalculateFrame()\n }\n \n this.applyStyles()\n }\n \n // \u2500\u2500 Override points \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n \n /**\n * Creates the content view and optionally a plain HTML label element.\n *\n * Default: returns a UIView containing a single <span> whose text is set\n * directly via innerHTML. The label element is returned so that setText()\n * can update it without going through the UIView system.\n *\n * Override to return a UIView-layout-based content view. In that case,\n * return labelElement: undefined \u2014 setText() will call the UIView's own\n * text-setting mechanism instead (override setText() too if needed).\n */\n protected createContentView(): { contentView: UIView; labelElement?: HTMLElement } {\n const labelElement = document.createElement(\"span\")\n labelElement.style.display = \"block\"\n labelElement.style.whiteSpace = \"pre-wrap\"\n labelElement.style.wordBreak = \"break-word\"\n labelElement.style.lineHeight = \"1.4\"\n labelElement.style.color = \"#ffffff\"\n labelElement.style.fontSize = \"12px\"\n labelElement.style.fontWeight = \"400\"\n // Padding is applied to the label element so it sits inset from the\n // container edges. The measurement in _measureHTMLLabelSize accounts\n // for this by adding padding * 2 to the measured width and height.\n const paddingPx = `${(UICore.main?.paddingLength ?? 16) * 0.5}px`\n labelElement.style.padding = paddingPx\n labelElement.style.boxSizing = \"border-box\"\n \n const contentView = new UIView()\n contentView.configureWithObject({\n backgroundColor: new UIColor(\"rgba(30, 30, 40, 0.96)\"),\n style: {\n borderRadius: \"5px\",\n boxShadow: \"0 4px 12px rgba(0,0,0,0.25)\"\n }\n })\n contentView.viewHTMLElement.appendChild(labelElement)\n \n return { contentView, labelElement }\n }\n \n /**\n * Apply styles to the default plain-HTML content view.\n * Only called when using the default createContentView() implementation.\n * Override alongside createContentView() if you provide a UIView-based\n * content view that handles its own styling.\n */\n protected applyStyles() {}\n \n /**\n * Sets the tooltip text. Override if using a UIView-based content view\n * that exposes its own text-setting API.\n */\n setText(text: string) {\n if (this._labelElement) {\n this._labelElement.textContent = text\n }\n }\n \n // \u2500\u2500 Sizing \u2014 two-pass \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n \n /**\n * Returns the display size for the content view given the current text.\n *\n * For UIView-based content: delegates to intrinsicContentHeight/Width.\n * For plain-HTML content: measures the label element directly.\n *\n * Two passes:\n * 1. Height at maxWidth \u2014 determines how the text wraps\n * 2. Width at that height \u2014 finds the minimum width that fits the\n * wrapped text, so short text doesn't leave empty space on the right\n */\n protected measureContentSize(): { width: number; height: number } {\n if (this._labelElement) {\n return this._measureHTMLLabelSize()\n }\n const height = this.contentView.intrinsicContentHeight(this.maxWidth)\n const width = Math.min(this.contentView.intrinsicContentWidth(height), this.maxWidth)\n return { width, height }\n }\n \n private _measureHTMLLabelSize(): { width: number; height: number } {\n const label = this._labelElement!\n const prevMaxWidth = label.style.maxWidth\n const prevWidth = label.style.width\n \n // Force into DOM briefly if not connected\n let wasDetached = false\n if (!label.isConnected) {\n document.body.appendChild(label)\n wasDetached = true\n }\n \n // Pass 1: constrain to maxWidth to get the wrapped height.\n label.style.maxWidth = `${this.maxWidth}px`\n label.style.width = \"\"\n const wrappedHeight = label.scrollHeight\n \n // Pass 2: set both width:fit-content and max-width:innerMax together.\n // The browser reports the minimum width that fits the content without\n // exceeding the same constraint used in pass 1 \u2014 giving us the width\n // of the longest wrapped line rather than the full single-line width.\n // scrollWidth is used instead of getBoundingClientRect().width because\n // the label's parent UIView has no explicit width set at this point;\n // getBoundingClientRect() would return a value constrained by the\n // collapsed parent, resulting in near-zero width and extreme height.\n label.style.maxWidth = `${this.maxWidth}px`\n label.style.width = \"fit-content\"\n const naturalWidth = label.scrollWidth\n \n if (wasDetached) {\n document.body.removeChild(label)\n }\n \n label.style.maxWidth = prevMaxWidth\n label.style.width = prevWidth\n \n // The label has CSS padding applied (box-sizing: border-box), so\n // scrollHeight and getBoundingClientRect already include it.\n return {\n width: Math.min(Math.ceil(naturalWidth), this.maxWidth),\n height: wrappedHeight\n }\n }\n \n private get core() {\n return UICore.main\n }\n \n // \u2500\u2500 Frame calculation \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n \n private _recalculateFrame() {\n const { width, height } = this.measureContentSize()\n \n const viewportWidth = window.innerWidth\n const viewportHeight = window.innerHeight\n const margin = 8\n \n let left = this._mouseX + this.offsetX\n let top = this._mouseY + this.offsetY\n \n if (left + width > viewportWidth - margin) {\n left = this._mouseX - width - this.offsetX\n }\n if (top + height > viewportHeight - margin) {\n top = this._mouseY - height - this.offsetY * 0.5\n }\n \n left = Math.max(margin, left)\n top = Math.max(margin, top)\n \n // The element lives directly under document.body (no transformed ancestor),\n // so position: fixed + translate3d produced by setFrame() is viewport-relative.\n this.contentView.setFrame(\n this.contentView.frame\n .rectangleWithX(Math.round(left))\n .rectangleWithY(Math.round(top))\n .rectangleWithWidth(width)\n .rectangleWithHeight(height),\n 99999\n )\n }\n \n // \u2500\u2500 Init \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n \n private _ensureInitialized() {\n if (this._isInitialized) {\n return\n }\n this._isInitialized = true\n \n // Attach directly to document.body rather than as a UIView subview.\n // The framework writes all positions via translate3d transforms. Any ancestor\n // that carries a transform becomes the containing block for position:fixed\n // descendants, breaking viewport-relative placement. By appending the raw\n // element to document.body we guarantee no transformed ancestor exists,\n // so position:fixed + left/top always refer to the viewport.\n document.body.appendChild(this.contentView.viewHTMLElement)\n \n window.addEventListener(\"mousemove\", (event: MouseEvent) => {\n this._mouseX = event.clientX\n this._mouseY = event.clientY\n if (this._isVisible) {\n this.contentView.calculateAndSetViewFrame()\n }\n }, { passive: true })\n }\n \n // \u2500\u2500 Public API \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n \n show(text: string) {\n this._ensureInitialized()\n this.setText(text)\n this.contentView.viewHTMLElement.style.display = \"block\"\n if (!this._labelElement) {\n // UIView-based content needs a layout pass before measuring\n this.contentView.setNeedsLayout()\n UIView.layoutViewsIfNeeded()\n }\n this._isVisible = true\n this.contentView.calculateAndSetViewFrame()\n }\n \n hide() {\n this._isVisible = false\n this.contentView.viewHTMLElement.style.display = \"none\"\n }\n \n // \u2500\u2500 Attach / detach \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n \n attach(view: UIView, text: string) {\n this.detach(view)\n \n const enterHandler = (_sender: UIView, _event: Event) => {\n this.show(text)\n }\n const leaveHandler = (_sender: UIView, _event: Event) => {\n this.hide()\n }\n \n view.controlEventTargetAccumulator.PointerHover = enterHandler\n view.controlEventTargetAccumulator.PointerLeave = leaveHandler\n \n this._attachedHandlers.set(view, { enter: enterHandler, leave: leaveHandler })\n }\n \n detach(view: UIView) {\n const handlers = this._attachedHandlers.get(view)\n if (!handlers) {\n return\n }\n \n view.removeTargetForControlEvent(UIView.controlEvent.PointerHover, handlers.enter)\n view.removeTargetForControlEvent(UIView.controlEvent.PointerLeave, handlers.leave)\n \n this._attachedHandlers.delete(view)\n \n if (this._isVisible) {\n this.hide()\n }\n }\n \n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "uicore-ts",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.308",
|
|
4
4
|
"description": "UICore is a library to build native-like user interfaces using pure Typescript. No HTML is needed at all. Components are described as TS classes and all user interactions are handled explicitly. This library is strongly inspired by the UIKit framework that is used in IOS. In addition, UICore has tools to handle URL based routing, array sorting and filtering and adds a number of other utilities for convenience.",
|
|
5
5
|
"main": "compiledScripts/index.js",
|
|
6
6
|
"types": "compiledScripts/index.d.ts",
|
package/scripts/UITooltip.ts
CHANGED
|
@@ -184,9 +184,13 @@ export class UITooltip {
|
|
|
184
184
|
// The browser reports the minimum width that fits the content without
|
|
185
185
|
// exceeding the same constraint used in pass 1 — giving us the width
|
|
186
186
|
// of the longest wrapped line rather than the full single-line width.
|
|
187
|
+
// scrollWidth is used instead of getBoundingClientRect().width because
|
|
188
|
+
// the label's parent UIView has no explicit width set at this point;
|
|
189
|
+
// getBoundingClientRect() would return a value constrained by the
|
|
190
|
+
// collapsed parent, resulting in near-zero width and extreme height.
|
|
187
191
|
label.style.maxWidth = `${this.maxWidth}px`
|
|
188
192
|
label.style.width = "fit-content"
|
|
189
|
-
const naturalWidth = label.
|
|
193
|
+
const naturalWidth = label.scrollWidth
|
|
190
194
|
|
|
191
195
|
if (wasDetached) {
|
|
192
196
|
document.body.removeChild(label)
|
|
@@ -229,6 +233,8 @@ export class UITooltip {
|
|
|
229
233
|
left = Math.max(margin, left)
|
|
230
234
|
top = Math.max(margin, top)
|
|
231
235
|
|
|
236
|
+
// The element lives directly under document.body (no transformed ancestor),
|
|
237
|
+
// so position: fixed + translate3d produced by setFrame() is viewport-relative.
|
|
232
238
|
this.contentView.setFrame(
|
|
233
239
|
this.contentView.frame
|
|
234
240
|
.rectangleWithX(Math.round(left))
|
|
@@ -247,7 +253,13 @@ export class UITooltip {
|
|
|
247
253
|
}
|
|
248
254
|
this._isInitialized = true
|
|
249
255
|
|
|
250
|
-
|
|
256
|
+
// Attach directly to document.body rather than as a UIView subview.
|
|
257
|
+
// The framework writes all positions via translate3d transforms. Any ancestor
|
|
258
|
+
// that carries a transform becomes the containing block for position:fixed
|
|
259
|
+
// descendants, breaking viewport-relative placement. By appending the raw
|
|
260
|
+
// element to document.body we guarantee no transformed ancestor exists,
|
|
261
|
+
// so position:fixed + left/top always refer to the viewport.
|
|
262
|
+
document.body.appendChild(this.contentView.viewHTMLElement)
|
|
251
263
|
|
|
252
264
|
window.addEventListener("mousemove", (event: MouseEvent) => {
|
|
253
265
|
this._mouseX = event.clientX
|