Files
zeekayandHanzo Dev 69c6e860d7 transplant onto tamagui v2.6.0, brand as a re-runnable transform
This repo could never sync. There was NO merge-base with tamagui — 372 commits
here against 15,791 there, unrelated histories — because it began as a
copy-paste rebrand with a flattened layout. Every "sync" since was a hand
re-application that drifted, which is why the tree sat on 7.3.1 while upstream
shipped fixes we then rediscovered by hand.

This commit's PARENT IS TAMAGUI. A future sync is `git merge tama/main` then
`node scripts/rebrand.mjs` — a real merge, because the history is now shared.

174 packages: 166 upstream, our 8 restored (chrome, shell, telemetry, recipes,
get, font-geist-{sans,mono}, @hanzo/booker), bento-get gained. 170/170 build.

Our patches to files UPSTREAM ALSO OWNS are carried, which the package-level
delta missed and which was where the value actually was:
  - useMedia's server snapshot (`initState ?? getMedia()`). Upstream still
    returns undefined there, so every Tamagui Dialog kills a prerender — the
    exact crash hanzo.app spent a day on.
  - data-slot on web host nodes, absent upstream entirely.
Both applied ONTO upstream's newer files (it rewrote useMedia +150/-63 and
createComponent +160/-55), marked `PATCH (ours, carried across the transplant)`.

Seven traps are encoded in scripts/rebrand.mjs, each found by a gate:
skip itself and lockfiles; special-case the double-branded names in BOTH scoped
and bare form; map path strings, not just paths; drop a level for the flattened
group; check disk rather than a stale index.

Rust build artifacts are NOT committed. An earlier draft of this commit swept
apps/desktop/src-tauri/target/debug in via `git add -A` — 3,859 files including
a 272MB .a, over GitHub's 100MB cap, so the push could never have landed. They
are gitignored now.

Co-Authored-By: Hanzo Dev <[email protected]>
2026-07-30 18:25:06 -07:00

436 lines
12 KiB
JavaScript

/**
* Sticksy.js
* A library for making cool things like fixed widgets.
* Dependency-free. ES5 code.
* -
* @version 0.2.0
* @url https://github.com/kovart/sticksy
* @author Artem Kovalchuk <[email protected]>
* @license The MIT License (MIT)
*/
const States = {
STATIC: 'static',
FIXED: 'fixed',
STUCK: 'stuck',
}
/**
* Constructor function for Sticksy
* @param {(string|Element)} target Sticky element or query
* @param {Object=} options Options object
* @param {boolean=} options.listen Listen for DOM changes in the container
* @param {boolean=} options.enabled Should the element be sticky immediately
* @param {number=} options.topSpacing Top indent when element has 'fixed' state
* @constructor
*/
export function Sticksy(target, options) {
if (!target) throw new Error('You have to specify the target element')
if (typeof target !== 'string' && !(target instanceof Element))
throw new Error(
'Expected a string or element, but got: ' + Object.prototype.toString.call(target)
)
const targetEl = Utils.findElement(target)
if (!targetEl) throw new Error('Cannot find target element: ' + target)
const containerEl = targetEl.parentNode
if (!containerEl) throw new Error('Cannot find container of target element: ' + target)
options = options || {}
this._props = {
containerEl: containerEl,
targetEl: targetEl,
topSpacing: options.topSpacing || 0,
enabled: options.enabled || true,
listen: options.listen || false, // listen for the DOM changes in the container
}
/**
* Called when state of sticky element has changed
* @type {Function|null} Callback
*/
this.onStateChanged = null
/**
* DOM Element reference of sticky element
* @type {Element} nodeRef
*/
this.nodeRef = targetEl
this._initialize()
}
// Static variables
Sticksy.instances = []
Sticksy.enabledInstances = []
Sticksy.prototype._initialize = function () {
// default state
this.state = States.STATIC
this._stickyNodes = []
// Since sticky nodes may have a fixed position,
// the library injects cloned (dummy) nodes to avoid problems with a DOM flow
this._dummyNodes = []
// Sticky element makes lower elements sticky also
let sibling = this._props.targetEl
while (sibling) {
const clone = sibling.cloneNode(true)
clone.style.visibility = 'hidden'
clone.style.pointerEvents = 'none'
clone.className += ' sticksy-dummy-node'
clone.removeAttribute('id')
this._props.targetEl.parentNode.insertBefore(clone, this._props.targetEl)
this._stickyNodes.push(sibling)
this._dummyNodes.push(clone)
sibling = sibling.nextElementSibling
}
// Used when we calc the state of elements
this._stickyNodesHeight = 0
// Positions of the scroll when the elements are fixed (sticky)
this._limits = {
top: 0,
bottom: 0,
}
// MutationObserver state
this._isListening = false
// The library uses 'position: absolute' to stuck sticky nodes to the bottom of the container
this._props.containerEl.style.position = 'relative'
// Flexbox doesn't collapse margin of items
this._shouldCollapseMargins =
getComputedStyle(this._props.containerEl).display.indexOf('flex') === -1
// Listen for DOM changes in the container
if (this._props.listen) {
this._mutationObserver = new MutationObserver(() => {
this.hardRefresh()
})
this._startListen()
}
Sticksy.instances.push(this)
if (this._props.enabled) {
Sticksy.enabledInstances.push(this)
}
this.hardRefresh()
}
Sticksy.prototype._startListen = function () {
if (!this._props.listen || this._isListening) return
this._mutationObserver.observe(this._props.containerEl, {
attributes: true,
characterData: true,
childList: true,
subtree: true,
})
this._isListening = true
}
Sticksy.prototype._stopListen = function () {
if (!this._props.listen || !this._isListening) return
this._mutationObserver.disconnect()
this._isListening = false
}
Sticksy.prototype._calcState = function (windowOffset) {
if (windowOffset < this._limits.top) {
return States.STATIC
} else if (windowOffset >= this._limits.bottom) {
return States.STUCK
}
return States.FIXED
}
Sticksy.prototype._updateStickyNodesHeight = function () {
this._stickyNodesHeight =
Utils.getComputedBox(this._stickyNodes[this._stickyNodes.length - 1])
.bottomWithMargin - Utils.getComputedBox(this._stickyNodes[0]).topWithMargin
}
Sticksy.prototype._updateLimits = function () {
const containerEl = this._props.containerEl
const stickyNodes = this._stickyNodes
const containerBox = Utils.getComputedBox(containerEl)
const topNodeBox = Utils.getComputedBox(stickyNodes[0])
this._limits = {
top: topNodeBox.topWithMargin - this._props.topSpacing,
bottom:
containerBox.bottom -
containerBox.paddingBottom -
this._props.topSpacing -
this._stickyNodesHeight,
}
}
Sticksy.prototype._applyState = function (state) {
// We enable dummy nodes at the end
// to avoid 'scrolling down effect' in Chrome
if (state === States.STATIC) {
this._resetElements(this._stickyNodes)
this._disableElements(this._dummyNodes)
} else {
this._fixElementsSize(this._stickyNodes)
if (state === States.FIXED) {
this._fixElements(this._stickyNodes)
} else {
this._stuckElements(this._stickyNodes)
}
this._enableElements(this._dummyNodes)
}
}
/**
* Recalculate the position
* @public
*/
Sticksy.prototype.refresh = function () {
const state = this._calcState(window.pageYOffset, this._limits)
if (state === this.state) return
this.state = state
this._stopListen()
this._applyState(state)
this._startListen()
if (typeof this.onStateChanged === 'function') {
this.onStateChanged(state)
}
}
/**
* Reset, recalculate and then update the position
* @public
*/
Sticksy.prototype.hardRefresh = function () {
this._stopListen()
const oldState = this.state
// reset state for recalculation
this.state = States.STATIC
this._applyState(this.state)
this._fixElementsSize(this._stickyNodes)
this._updateStickyNodesHeight()
this._updateLimits()
this.state = this._calcState(window.pageYOffset, this._limits)
this._applyState(this.state)
this._startListen()
if (typeof this.onStateChanged === 'function' && oldState !== this.state) {
this.onStateChanged(this.state)
}
}
/**
* Enable 'sticky' effect
* @public
*/
Sticksy.prototype.enable = function () {
this._props.enabled = true
Sticksy.enabledInstances.push(this)
this.hardRefresh()
}
/**
* Disable 'sticky' effect
* @public
*/
Sticksy.prototype.disable = function () {
this._props.enabled = false
this.state = States.STATIC
this._applyState(this.state)
Sticksy.enabledInstances.splice(Sticksy.enabledInstances.indexOf(this), 1)
}
/* --------------------------
* Operations with elements
* -------------------------- */
Sticksy.prototype._fixElements = function (elements) {
let previousMarginBottom = 0 // for margins collapse
let offset = this._props.topSpacing
for (let i = 0; i < elements.length; i++) {
const el = elements[i]
const box = Utils.getComputedBox(el)
const extraMarginTop = this._shouldCollapseMargins
? Math.max(0, previousMarginBottom - box.marginTop)
: previousMarginBottom
el.style.position = 'fixed'
el.style.top = offset + extraMarginTop + 'px'
el.style.bottom = ''
offset += box.height + box.marginTop + extraMarginTop
previousMarginBottom = box.marginBottom
}
}
Sticksy.prototype._stuckElements = function (elements) {
let previousMarginTop = 0 // for margins collapse
// we add offset because the container padding doesn't affect on absolute position
let offset = Utils.getComputedBox(this._props.containerEl).paddingBottom
for (let i = elements.length - 1; i >= 0; i--) {
const el = elements[i]
const box = Utils.getComputedBox(el)
const extraMarginBottom = this._shouldCollapseMargins
? Math.max(0, previousMarginTop - box.marginBottom)
: previousMarginTop
el.style.position = 'absolute'
el.style.top = 'auto'
el.style.bottom = offset + extraMarginBottom + 'px'
offset += box.height + box.marginBottom + extraMarginBottom
previousMarginTop = box.marginTop
}
}
Sticksy.prototype._resetElements = (elements) => {
elements.forEach((el) => {
el.style.position = ''
el.style.top = ''
el.style.bottom = ''
el.style.height = ''
el.style.width = ''
})
}
Sticksy.prototype._disableElements = (elements) => {
elements.forEach((el) => {
el.style.display = 'none'
})
}
Sticksy.prototype._enableElements = function (elements) {
for (let i = 0; i < elements.length; i++) {
elements[i].style.display = getComputedStyle(this._stickyNodes[i]).display
}
}
Sticksy.prototype._fixElementsSize = function () {
for (let i = 0; i < this._stickyNodes.length; i++) {
const stickyNode = this._stickyNodes[i]
const style = getComputedStyle(stickyNode)
stickyNode.style.width = style.width
stickyNode.style.height = style.height
}
}
/* ------------------------
* Static methods
* ------------------------ */
Sticksy.refreshAll = () => {
for (let i = 0; i < Sticksy.enabledInstances.length; i++) {
Sticksy.enabledInstances[i].refresh()
}
}
Sticksy.hardRefreshAll = () => {
for (let i = 0; i < Sticksy.enabledInstances.length; i++) {
Sticksy.enabledInstances[i].hardRefresh()
}
}
Sticksy.enableAll = function () {
Sticksy.enabledInstances = Sticksy.instances.slice()
this.hardRefreshAll()
}
Sticksy.disableAll = () => {
const copy = Sticksy.enabledInstances.slice()
for (let i = 0; i < copy.length; i++) {
Sticksy.enabledInstances[i].disable()
}
Sticksy.enabledInstances = []
}
/**
* Initialize all sticky elements
* @param {string|Element|Element[]|jQuery} target - Query string, DOM elements or JQuery object
* @param {{topSpacing: number=, listen: boolean=}=} options - Constructor options
* @param {boolean=} ignoreNothingFound - Don't throw an error if there are no elements that satisfying selector.
* @returns {(Sticksy)[]}
*/
Sticksy.initializeAll = (target, options, ignoreNothingFound) => {
if (typeof target === 'undefined') throw new Error("'target' parameter is undefined")
let elements = []
if (target instanceof Element) {
elements = [target]
} else if (
typeof target.length !== 'undefined' &&
target.length > 0 &&
target[0] instanceof Element
) {
// check if JQuery object and fetch native DOM elements
elements = typeof target.get === 'function' ? target.get() : target
} else if (typeof target === 'string') {
elements = document.querySelectorAll(target) || []
}
// There may be situations when we have several sticky elements in one parent
// To resolve this situation we take only the first element
const parents = []
const stickyElements = []
elements.forEach((el) => {
if (parents.indexOf(el.parentNode) !== -1) return
parents.push(el.parentNode)
stickyElements.push(el)
})
if (!ignoreNothingFound && !stickyElements.length)
throw new Error('There are no elements to initialize')
return stickyElements.map((el) => new Sticksy(el, options))
}
/* ------------------------
* Refresh events
* ------------------------ */
if (typeof window !== 'undefined') {
window.addEventListener('scroll', Sticksy.refreshAll)
window.addEventListener('resize', Sticksy.hardRefreshAll)
}
/* ------------------------
* Helpers
* ------------------------ */
const Utils = {
parseNumber: (val) => Number.parseFloat(val) || 0,
findElement: (el, root) => {
if (!root) root = document
return 'string' === typeof el
? root.querySelector(el)
: el instanceof Element
? el
: undefined
},
getComputedBox: (elem) => {
const box = elem.getBoundingClientRect()
const style = getComputedStyle(elem)
return {
height: box.height,
width: box.width,
top: window.pageYOffset + box.top,
bottom: window.pageYOffset + box.bottom,
marginTop: Utils.parseNumber(style.marginTop),
marginBottom: Utils.parseNumber(style.marginBottom),
paddingTop: Utils.parseNumber(style.paddingTop),
paddingBottom: Utils.parseNumber(style.paddingBottom),
topWithMargin: window.pageYOffset + box.top - Utils.parseNumber(style.marginTop),
bottomWithMargin:
window.pageYOffset + box.bottom + Utils.parseNumber(style.marginBottom),
}
},
}