uicore-ts 1.1.307 → 1.1.310

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.getBoundingClientRect().width;
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
- this.contentView.addedAsSubviewToView(import_UICore.UICore.main.rootViewController.view);
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": "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",
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.307",
3
+ "version": "1.1.310",
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",
@@ -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.getBoundingClientRect().width
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
- this.contentView.addedAsSubviewToView(UICore.main.rootViewController.view)
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