index-scrollbar
Advanced tools
Comparing version 0.0.12 to 1.0.0
@@ -5,5 +5,5 @@ 'use strict'; | ||
const index = require('./index-c184af0b.js'); | ||
const index = require('./index-a83d2d61.js'); | ||
const indexScrollbarCss = ":host{transition:opacity 0.2s ease-in-out}:host .container{height:100%;font-size:70%;display:flex;flex-direction:column;align-items:center;justify-content:space-around}:host .container .letter{position:relative;pointer-events:none}:host .container .letter label{position:absolute;top:50%;left:50%;transform:translate(-50%, -50%);transition:transform 0.15s;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;}:host .container .letter.active label{font-weight:500}"; | ||
const indexScrollbarCss = ":host{top:0;bottom:0;font-size:min(20px, max(12px, 1vh));padding:calc(2 * min(20px, max(12px, 1vh))) 0}:host .container{height:100%;display:flex;flex-direction:column;align-items:center;justify-content:space-between}:host .container.cursor-pointer{cursor:pointer}:host .container .letter{padding:0 20px;position:relative;pointer-events:none;transition:transform 0.2s ease-in-out;transform-origin:60%}:host .container .letter label{position:absolute;top:50%;left:50%;transform:translate(-50%, -50%)}:host .container .letter.letter-disabled{opacity:0.3}:host .container .letter.letter-is-hidden-value{transform-origin:center;transform:scale(2)}"; | ||
@@ -14,81 +14,149 @@ let IndexScrollbar = class { | ||
this.letterChange = index.createEvent(this, "letterChange", 7); | ||
this.scrolling = index.createEvent(this, "scrolling", 7); | ||
this.isActive = index.createEvent(this, "isActive", 7); | ||
//A custom alphabet to be used instead of the default alphabet. Default is 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' | ||
this.alphabet = [...'ABCDEFGHIJKLMNOPQRSTUVWXYZ']; | ||
//A custom overflow divider. Can be undefined or null if you don't want to use one. Defaults to '·' | ||
this.overflowDivider = '·'; | ||
//Valid letters that are available for the user to select. default is all letters | ||
this.validLetters = this.alphabet; | ||
//Whether or invalid letters should be disabled (greyed out and do not magnify) | ||
this.disableInvalidLetters = false; | ||
//Whether or invalid letters should be disabled (greyed out and do not magnify) | ||
this.prioritizeHidingInvalidLetters = false; | ||
//Whether or not letters should be magnified | ||
this.letterMagnification = true; | ||
//Whether or not overflow diveders should be magnified | ||
this.magnifyDividers = false; | ||
//The maximum that the magnification multiplier can be. Default is 3 | ||
this.magnificationMultiplier = 2; | ||
//Magnification curve accepts an array of numbers between 1 and 0 that represets the curve of magnification starting from magnificaiton multiplier to 1: defaults to [1, 0.9, 0.8, 0.7, 0.6, 0.5, 0.4, 0.3, 0.2, 0.1] | ||
this.magnificationCurve = [1, 0.7, 0.5, 0.3, 0.1]; | ||
//If the scrolling for touch screens in the x direction should be lenient. Default is false | ||
this.exactX = false; | ||
this.fontSize = 16; | ||
this.magnificationSize = 32; | ||
this.alphabet = 'abcdefghijklmnopqrstuvwxyz'.toUpperCase().split(''); | ||
this.validLetters = undefined; | ||
//Whether or not letter change event is emitted on mouse hover. Default is false | ||
this.navigateOnHover = false; | ||
this.active = false; | ||
this.rendering = true; | ||
//Percentage or number in pixels of how far apart the letters are. Defaults to 1.75% | ||
this.letterSpacing = 0; | ||
//This interval can be used for fast, regular size-checks | ||
//Useful, if e.g. a splitter-component resizes the scroll-bar but not the window itself. Set in ms and defaults to 0 (disabled) | ||
this.offsetSizeCheckInterval = 0; | ||
this._lastEmittedActive = false; | ||
this._isComponentActive = false; | ||
this.visibleLetters = []; | ||
//Flag for determining letter under pointer | ||
this._lettersShortened = false; | ||
this._isInBounds = true; | ||
} | ||
onAlphabetChange(value) { | ||
if (!(Array.isArray(value) && value.every(it => typeof it === 'string'))) | ||
throw new Error('alphabet must be a string or an array of strings'); | ||
this.checkVisibleLetters(true); | ||
this.visibleLetters = value; | ||
} | ||
onOverflowDividerChange(value) { | ||
if (!(typeof value === 'string' || value === undefined || value === null)) | ||
throw new Error('overflowDivider must be a string'); | ||
this.checkVisibleLetters(true); | ||
} | ||
onValidLettersChange() { | ||
this.checkVisibleLetters(true); | ||
} | ||
onDisableInvalidLettersChange() { | ||
this.checkVisibleLetters(true); | ||
} | ||
onPrioritizeHidingInvalidLettersChange() { | ||
this.checkVisibleLetters(true); | ||
} | ||
onMagnificationMultiplierChange() { | ||
this.checkVisibleLetters(true); | ||
} | ||
onMagnificationCurveChange(value) { | ||
if (!(Array.isArray(value) && value.every(it => typeof it === 'number' && it >= 0 && it <= 1))) | ||
throw new Error('magnificationCurve must be an array of numbers between 0 and 1'); | ||
} | ||
onExactXChange() { | ||
this._isInBounds = true; | ||
} | ||
onLetterSpacingChange(value) { | ||
if (!(typeof value === 'number' || typeof value === 'string' || value === null)) | ||
throw new Error('letterSpacing must be a number, string or null'); | ||
this.checkVisibleLetters(true); | ||
} | ||
onOffsetSizeCheckIntervalChange(value) { | ||
if (this._offsetSizeCheckIntervalTimer) | ||
clearInterval(this._offsetSizeCheckIntervalTimer); | ||
this.offsetSizeCheckInterval = value; | ||
this.offsetSizeCheckInterval && ((this._offsetSizeCheckIntervalTimer = setInterval(() => this.checkVisibleLetters())), this.offsetSizeCheckInterval); | ||
} | ||
connectedCallback() { | ||
window.addEventListener('resize', this.onResize.bind(this)); | ||
window.addEventListener('resize', this.checkVisibleLetters.bind(this)); | ||
this.visibleLetters = this.alphabet; | ||
} | ||
componentDidLoad() { | ||
this.el.shadowRoot.querySelector('.container').addEventListener('touchstart', this.touchStart.bind(this), { passive: true }); | ||
this.el.shadowRoot.querySelector('.container').addEventListener('touchmove', this.touchMove.bind(this), { passive: true }); | ||
this.el.shadowRoot.querySelector('.container').addEventListener('touchend', this.touchEnd.bind(this), { passive: true }); | ||
this.el.shadowRoot.querySelector('.container').addEventListener('mouseenter', this.mouseEnter.bind(this), { passive: true }); | ||
this.el.shadowRoot.querySelector('.container').addEventListener('mousemove', this.mouseMove.bind(this), { passive: true }); | ||
this.el.shadowRoot.querySelector('.container').addEventListener('mouseleave', this.mouseLeave.bind(this), { passive: true }); | ||
this.el.shadowRoot.querySelector('.container').addEventListener('click', this.click.bind(this)); | ||
// I hate this | ||
setTimeout(() => { | ||
this.interval = setInterval(() => { | ||
var _a; | ||
let height = (_a = this.el.shadowRoot.querySelector('.container')) === null || _a === void 0 ? void 0 : _a.clientHeight; | ||
if (height !== this.height) { | ||
this.height = height; | ||
if (height) { | ||
this.onResize(); | ||
this.rendering = false; | ||
} | ||
else { | ||
this.rendering = true; | ||
} | ||
} | ||
}, 100); | ||
}, 100); | ||
this.alphabetContainer.addEventListener('touchstart', ev => this.focusEvent(ev, 'touchstart'), { passive: true }); | ||
this.alphabetContainer.addEventListener('touchmove', ev => this.focusEvent(ev, 'touchmove'), { passive: true }); | ||
this.alphabetContainer.addEventListener('touchend', () => this.focusEnd(), { passive: true }); | ||
this.alphabetContainer.addEventListener('mouseenter', ev => this.focusEvent(ev, 'mouseenter'), { passive: true }); | ||
this.alphabetContainer.addEventListener('mousemove', ev => this.focusEvent(ev, 'mousemove'), { passive: true }); | ||
this.alphabetContainer.addEventListener('mouseleave', () => this.focusEnd(), { passive: true }); | ||
this.alphabetContainer.addEventListener('click', ev => this.focusEvent(ev, 'click'), { passive: true }); | ||
this.alphabetContainer.addEventListener('mouseenter', () => this.focusEnd(), { passive: true }); | ||
this.checkVisibleLetters(true); | ||
} | ||
disconnectedCallback() { | ||
this.el.shadowRoot.querySelector('.container').removeEventListener('touchstart', this.touchStart.bind(this)); | ||
this.el.shadowRoot.querySelector('.container').removeEventListener('touchmove', this.touchMove.bind(this)); | ||
this.el.shadowRoot.querySelector('.container').removeEventListener('touchend', this.touchEnd.bind(this)); | ||
this.el.shadowRoot.querySelector('.container').removeEventListener('mouseenter', this.mouseEnter.bind(this)); | ||
this.el.shadowRoot.querySelector('.container').removeEventListener('mousemove', this.mouseMove.bind(this)); | ||
this.el.shadowRoot.querySelector('.container').removeEventListener('mouseleave', this.mouseLeave.bind(this)); | ||
this.el.shadowRoot.querySelector('.container').removeEventListener('click', this.click.bind(this)); | ||
window.removeEventListener('resize', this.onResize.bind(this)); | ||
clearInterval(this.interval); | ||
this.alphabetContainer.removeEventListener('touchstart', ev => this.focusEvent(ev, 'touchstart')); | ||
this.alphabetContainer.removeEventListener('touchmove', ev => this.focusEvent(ev, 'touchmove')); | ||
this.alphabetContainer.removeEventListener('touchend', () => this.focusEnd()); | ||
this.alphabetContainer.removeEventListener('mouseenter', ev => this.focusEvent(ev, 'mouseenter')); | ||
this.alphabetContainer.removeEventListener('mousemove', ev => this.focusEvent(ev, 'mousemove')); | ||
this.alphabetContainer.removeEventListener('mouseleave', () => this.focusEnd()); | ||
this.alphabetContainer.removeEventListener('click', ev => this.focusEvent(ev, 'click')); | ||
window.removeEventListener('resize', this.checkVisibleLetters.bind(this)); | ||
clearInterval(this._offsetSizeCheckIntervalTimer); | ||
} | ||
onResize() { | ||
get alphabetContainer() { | ||
return this.el.shadowRoot.querySelector('.container'); | ||
} | ||
checkVisibleLetters(force) { | ||
let height = this.alphabetContainer.clientHeight; | ||
if (!force && height === this._lastHeight) { | ||
return; | ||
} | ||
this._lastHeight = height; | ||
let newAlphabet = this.alphabet; | ||
const height = this.el.shadowRoot.querySelector('.container').clientHeight; | ||
// const newLetterIndex = Math.ceil((this.letterIndex * height) / letterSize); | ||
// this.letterIndex = newLetterIndex; | ||
// this.onLetterIndexChange(newLetterIndex); | ||
const numLetters = this.alphabet.length; | ||
const basicLettersSize = this.fontSize * (numLetters - 5 > 0 ? numLetters - 5 : 0); | ||
let specialLettersSize = this.magnificationSize; | ||
for (let i = 0; i < 5; i++) { | ||
specialLettersSize += this.getMultiplier(i) * this.magnificationSize * 2; | ||
let letterSpacing = 0; | ||
let letterSize = this.stringToNumber(getComputedStyle(this.alphabetContainer).getPropertyValue('font-size')); | ||
if (this.letterMagnification) { | ||
letterSize = letterSize * this.magnificationMultiplier * 0.6; | ||
} | ||
const averageLetterSize = (basicLettersSize + specialLettersSize) / numLetters; | ||
if (height / averageLetterSize < numLetters) { | ||
let numHiddenLetters = numLetters - Math.floor(height / averageLetterSize); | ||
//Calculate actual letter spacing | ||
if (typeof this.letterSpacing === 'number') { | ||
letterSpacing = this.letterSpacing; | ||
} | ||
else if (typeof this.letterSpacing === 'string') { | ||
letterSpacing = this.stringToNumber(this.letterSpacing); | ||
if (this.letterSpacing.endsWith('%')) { | ||
letterSpacing = height * (letterSpacing / 100); | ||
} | ||
} | ||
letterSize = letterSize + letterSpacing; | ||
//Remove invalid letters (if set and necessary) | ||
if (this.prioritizeHidingInvalidLetters && !!this.validLetters && height / letterSize < newAlphabet.length) { | ||
newAlphabet = this.validLetters; | ||
} | ||
//Check if there is enough free space for letters | ||
this._lettersShortened = height / letterSize < newAlphabet.length; | ||
if (this._lettersShortened) { | ||
const numHiddenLetters = newAlphabet.length - Math.floor(height / letterSize); | ||
if (numHiddenLetters === newAlphabet.length) | ||
newAlphabet = []; | ||
//determine how many letters to hide | ||
const hiddenHalves = this.getNumHiddenHalves(numHiddenLetters, numLetters) + 1; | ||
const hiddenHalves = this.getNumHiddenHalves(numHiddenLetters, newAlphabet.length) + 1; | ||
// (this.magnifyDividers || numHiddenLetters > newAlphabet.length - 2 ? 1 : 0); | ||
//split alphabet into two halves | ||
let alphabet1 = this.alphabet.slice(0, Math.ceil(numLetters / 2)); | ||
let alphabet2 = this.alphabet.slice(Math.floor(numLetters / 2)).reverse(); | ||
let alphabet1 = newAlphabet.slice(0, Math.ceil(newAlphabet.length / 2)); | ||
let alphabet2 = newAlphabet.slice(Math.floor(newAlphabet.length / 2)).reverse(); | ||
for (let i = 0; i < hiddenHalves; i++) { | ||
alphabet1 = alphabet1.filter((_, index) => { | ||
return index % 2 === 0; | ||
}); | ||
alphabet2 = alphabet2.filter((_, index) => { | ||
return index % 2 === 0; | ||
}); | ||
alphabet1 = alphabet1.filter((_, i) => i % 2 === 0); | ||
alphabet2 = alphabet2.filter((_, i) => i % 2 === 0); | ||
} | ||
@@ -98,3 +166,4 @@ //insert dots between letters | ||
if (i > 0) { | ||
prev.push('·'); | ||
if (this.overflowDivider) | ||
prev.push(this.overflowDivider); | ||
} | ||
@@ -104,6 +173,6 @@ prev.push(curr); | ||
}, []); | ||
//insert dots between letters | ||
alphabet2 = alphabet2.reduce((prev, curr, i) => { | ||
if (i > 0) { | ||
prev.push('·'); | ||
if (this.overflowDivider) | ||
prev.push(this.overflowDivider); | ||
} | ||
@@ -113,4 +182,4 @@ prev.push(curr); | ||
}, []); | ||
if (this.alphabet.length % 2 === 0) | ||
alphabet1.push('·'); | ||
if (this.alphabet.length % 2 === 0 && this.overflowDivider) | ||
alphabet1.push(this.overflowDivider); | ||
newAlphabet = alphabet1.concat(alphabet2.reverse()); | ||
@@ -126,129 +195,111 @@ } | ||
} | ||
isActive(i) { | ||
return this.letterIndex === i + 1 && this.active; | ||
isValid(letter) { | ||
var _a; | ||
return ((_a = this.validLetters) === null || _a === void 0 ? void 0 : _a.includes(letter)) !== false || letter === this.overflowDivider; | ||
} | ||
getMultiplier(i) { | ||
if (!this.active || !this.letterMagnification || (this.visibleLetters.length > i && this.visibleLetters[i] === '·')) | ||
return 1; | ||
const multiplier = this.magnificationSize / this.fontSize; | ||
if (this.letterIndex === i + 1 && this.active) | ||
return multiplier; | ||
else if (i + 1 === this.letterIndex - 1 || i + 1 === this.letterIndex + 1) | ||
return multiplier * 0.8; | ||
else if (i + 1 === this.letterIndex - 2 || i + 1 === this.letterIndex + 2) | ||
return multiplier * 0.7; | ||
else if (i + 1 === this.letterIndex - 3 || i + 1 === this.letterIndex + 3) | ||
return multiplier * 0.6; | ||
else | ||
return 1; | ||
} | ||
//*** Touch Events ***// | ||
touchStart(event) { | ||
this.scrolling.emit(true); | ||
this.active = true; | ||
this.touchMove(event); | ||
} | ||
//updates on every form of touch | ||
touchMove(event) { | ||
const x = event.touches[0].clientX; | ||
const y = event.touches[0].clientY; | ||
const newLetterIndex = this.getLetterIndexFromCoordinates(x, y); | ||
if (newLetterIndex) | ||
this.onLetterIndexChange(newLetterIndex); | ||
} | ||
touchEnd() { | ||
this.lastLetterIndex = null; | ||
this.scrolling.emit(false); | ||
this.active = false; | ||
} | ||
//*** End Touch Events ***// | ||
//*** Mouse Events ***// | ||
mouseEnter(event) { | ||
if (this.navigateOnHover) { | ||
this.scrolling.emit(true); | ||
focusEvent(event, type) { | ||
var _a, _b, _c, _d; | ||
if (!this._lastEmittedActive) { | ||
this.isActive.emit((this._lastEmittedActive = true)); | ||
} | ||
this.active = true; | ||
this.mouseMove(event); | ||
} | ||
mouseMove(event) { | ||
const x = event.clientX; | ||
const y = event.clientY; | ||
const newLetterIndex = this.getLetterIndexFromCoordinates(x, y); | ||
if (!this.navigateOnHover) | ||
return; | ||
if (newLetterIndex) | ||
this.onLetterIndexChange(newLetterIndex); | ||
} | ||
mouseLeave() { | ||
if (this.navigateOnHover) { | ||
this.scrolling.emit(false); | ||
if (type === 'click') | ||
this._isComponentActive = false; | ||
else if (!this._isComponentActive) | ||
this._isComponentActive = true; | ||
this.setLetterFromCoordinates((_b = (_a = event.touches) === null || _a === void 0 ? void 0 : _a[0].clientX) !== null && _b !== void 0 ? _b : event.clientX, (_d = (_c = event.touches) === null || _c === void 0 ? void 0 : _c[0].clientY) !== null && _d !== void 0 ? _d : event.clientY); | ||
if (this._lastEmittedLetter !== this.letterSelected && (this.navigateOnHover || !type.includes('mouse'))) { | ||
this.letterChange.emit((this._lastEmittedLetter = this.letterSelected)); | ||
} | ||
this.lastLetterIndex = null; | ||
this.active = false; | ||
} | ||
click(event) { | ||
event.preventDefault(); | ||
this.lastLetterIndex = null; | ||
const x = event.clientX; | ||
const y = event.clientY; | ||
const newLetterIndex = this.getLetterIndexFromCoordinates(x, y); | ||
this.onLetterIndexChange(newLetterIndex); | ||
focusEnd() { | ||
this.isActive.emit((this._isComponentActive = this._lastEmittedActive = false)); | ||
} | ||
//*** End Mouse Events ***// | ||
//*** Logic ***// | ||
getLetterIndexFromCoordinates(x, y) { | ||
let letterIndex = null; | ||
const aTop = this.el.shadowRoot.querySelector('.container').getBoundingClientRect().top; | ||
const aHeight = this.el.shadowRoot.querySelector('.container').clientHeight; | ||
const index = Math.ceil(((y - aTop) / aHeight) * this.alphabet.length); | ||
const aRightX = this.el.shadowRoot.querySelector('.container').getBoundingClientRect().right; | ||
const aLeftX = this.el.shadowRoot.querySelector('.container').getBoundingClientRect().left; | ||
if (index <= this.alphabet.length && index > 0) { | ||
if (this.exactX) { | ||
if (x < aRightX && x > aLeftX) { | ||
letterIndex = Math.ceil(index); | ||
} | ||
setLetterFromCoordinates(x, y) { | ||
if (this.exactX) { | ||
const rightX = this.alphabetContainer.getBoundingClientRect().right; | ||
const leftX = this.alphabetContainer.getBoundingClientRect().left; | ||
this._isInBounds = x > leftX && x < rightX; | ||
if (!this._isInBounds) { | ||
this.visualLetterIndex = this.visualLetterIndex = null; | ||
return; | ||
} | ||
} | ||
const height = this.alphabetContainer.clientHeight; | ||
//Letters drew outside the viewport or host padding may cause values outsize height boundries (Usage of min/max) | ||
const top = Math.min(Math.max(0, y - this.alphabetContainer.getBoundingClientRect().top), height); | ||
let topRelative = (top / height) * (this.visibleLetters.length - 1); | ||
const preferNext = Math.round(topRelative) < topRelative; | ||
topRelative = Math.round(topRelative); | ||
this.magIndex = topRelative; | ||
//Set visualLetterIndex to the closest valid letter | ||
this.visualLetterIndex = this.getClosestValidLetterIndex(this.visibleLetters, topRelative, preferNext); | ||
if (this._lettersShortened) { | ||
if (this.validLetters) { | ||
this.letterSelected = this.validLetters[Math.round((top / height) * (this.validLetters.length - 1))]; | ||
} | ||
else { | ||
letterIndex = Math.ceil(index); | ||
this.letterSelected = this.alphabet[this.getClosestValidLetterIndex(this.alphabet, topRelative, preferNext)]; | ||
} | ||
} | ||
if (letterIndex) | ||
this.letterIndex = Math.round(letterIndex * (this.visibleLetters.length / this.alphabet.length)); | ||
return letterIndex; | ||
else { | ||
this.letterSelected = this.visibleLetters[this.visualLetterIndex]; | ||
} | ||
} | ||
//emits the letter change | ||
onLetterIndexChange(value) { | ||
if (!value) | ||
return; | ||
if (this.lastLetterIndex === value) | ||
return; | ||
this.lastLetterIndex = value; | ||
const letter = this.validLetters ? this.getClosestValidLetter(value) : this.alphabet[value - 1]; | ||
this.letterChange.emit(letter); | ||
getClosestValidLetterIndex(alphabet, visualLetterIndex, preferNext) { | ||
const lowercaseAlphabet = alphabet.map(l => l.toLowerCase()); | ||
const lowercaseValidLetters = this.validLetters.map(l => l.toLowerCase()); | ||
const validLettersAsNumbers = lowercaseValidLetters.map(l => lowercaseAlphabet.indexOf(l)); | ||
return validLettersAsNumbers.length > 0 | ||
? validLettersAsNumbers.reduce((prev, curr) => preferNext | ||
? Math.abs(curr - visualLetterIndex) > Math.abs(prev - visualLetterIndex) | ||
? prev | ||
: curr | ||
: Math.abs(curr - visualLetterIndex) < Math.abs(prev - visualLetterIndex) | ||
? curr | ||
: prev) | ||
: null; | ||
} | ||
//gets closest letter | ||
getClosestValidLetter(letterIndex) { | ||
const lowercaseAlphabet = this.alphabet.map(letter => letter.toLowerCase()); | ||
const lowercaseValidLetters = this.validLetters.map(letter => letter.toLowerCase()); | ||
const validLettersAsNumbers = lowercaseValidLetters.map(letter => lowercaseAlphabet.indexOf(letter) + 1); | ||
const closest = validLettersAsNumbers.reduce((prev, curr) => (Math.abs(curr - letterIndex) < Math.abs(prev - letterIndex) ? curr : prev)); | ||
return this.alphabet[closest - 1]; | ||
stringToNumber(value) { | ||
return Number(value === null || value === void 0 ? void 0 : value.match(/[\.\d]+/)[0]); | ||
} | ||
getLetterStyle(index) { | ||
if ((this.magIndex === undefined && this.magIndex === null) || | ||
(!this.magnifyDividers && this.visibleLetters[index] === this.overflowDivider) || | ||
(this.disableInvalidLetters && !this.isValid(this.visibleLetters[index]))) | ||
return {}; | ||
const lettersOnly = this.visibleLetters.filter(l => l !== this.overflowDivider); | ||
const mappedIndex = Math.round((index / this.visibleLetters.length) * lettersOnly.length); | ||
const mappedMagIndex = Math.round((this.magIndex / this.visibleLetters.length) * lettersOnly.length); | ||
let relativeIndex = this.magnifyDividers ? Math.abs(this.magIndex - index) : Math.abs(mappedMagIndex - mappedIndex); | ||
const magnification = relativeIndex < this.magnificationCurve.length - 1 ? this.magnificationCurve[relativeIndex] * (this.magnificationMultiplier - 1) + 1 : 1; | ||
const style = { | ||
transform: `scale(${magnification})`, | ||
zIndex: this.magIndex === index ? 1 : 0, | ||
}; | ||
return this._isInBounds && this._isComponentActive && this.letterMagnification ? style : { transform: 'scale(1)', zIndex: 0 }; | ||
} | ||
render() { | ||
return (index.h(index.Host, { style: { opacity: !this.rendering ? '1' : '0' } }, index.h("div", { class: { | ||
var _a; | ||
return (index.h(index.Host, null, index.h("div", { class: { | ||
container: true, | ||
}, style: { | ||
width: this.fontSize + 'px', | ||
} }, this.visibleLetters.map((letter, i) => { | ||
} }, (_a = this.visibleLetters) === null || _a === void 0 ? void 0 : _a.map((letter, i) => { | ||
return (index.h("div", { key: `${letter}-${i}`, class: { | ||
letter: true, | ||
active: this.isActive(i) && this.letterMagnification, | ||
}, id: this.visibleLetters[i] }, index.h("label", { style: { | ||
transform: `translate(-50%, -50%) scale(${this.getMultiplier(i)})`, | ||
fontSize: this.fontSize + 'px', | ||
} }, letter))); | ||
'letter': true, | ||
'letter-disabled': this.disableInvalidLetters && !this.isValid(letter), | ||
}, style: this.getLetterStyle(i), id: this.visibleLetters[i] }, index.h("label", null, " ", letter))); | ||
})))); | ||
} | ||
get el() { return index.getElement(this); } | ||
static get watchers() { return { | ||
"alphabet": ["onAlphabetChange"], | ||
"overflowDivider": ["onOverflowDividerChange"], | ||
"validLetters": ["onValidLettersChange"], | ||
"disableInvalidLetters": ["onDisableInvalidLettersChange"], | ||
"prioritizeHidingInvalidLetters": ["onPrioritizeHidingInvalidLettersChange"], | ||
"magnificationMultiplier": ["onMagnificationMultiplierChange"], | ||
"magnificationCurve": ["onMagnificationCurveChange"], | ||
"exactX": ["onExactXChange"], | ||
"letterSpacing": ["onLetterSpacingChange"], | ||
"offsetSizeCheckInterval": ["onOffsetSizeCheckIntervalChange"] | ||
}; } | ||
}; | ||
@@ -255,0 +306,0 @@ IndexScrollbar.style = indexScrollbarCss; |
'use strict'; | ||
const index = require('./index-c184af0b.js'); | ||
const index = require('./index-a83d2d61.js'); | ||
@@ -18,3 +18,3 @@ /* | ||
patchBrowser().then(options => { | ||
return index.bootstrapLazy([["index-scrollbar.cjs",[[1,"index-scrollbar",{"letterMagnification":[4,"letter-magnification"],"exactX":[4,"exact-x"],"fontSize":[2,"font-size"],"magnificationSize":[2,"magnification-size"],"alphabet":[16],"validLetters":[16],"navigateOnHover":[4,"navigate-on-hover"],"visibleLetters":[32],"letterIndex":[32],"active":[32],"rendering":[32]}]]]], options); | ||
return index.bootstrapLazy([["index-scrollbar.cjs",[[1,"index-scrollbar",{"alphabet":[16],"overflowDivider":[1,"overflow-divider"],"validLetters":[16],"disableInvalidLetters":[4,"disable-invalid-letters"],"prioritizeHidingInvalidLetters":[4,"prioritize-hiding-invalid-letters"],"letterMagnification":[4,"letter-magnification"],"magnifyDividers":[4,"magnify-dividers"],"magnificationMultiplier":[2,"magnification-multiplier"],"magnificationCurve":[16],"exactX":[4,"exact-x"],"navigateOnHover":[4,"navigate-on-hover"],"letterSpacing":[8,"letter-spacing"],"offsetSizeCheckInterval":[2,"offset-size-check-interval"],"_isComponentActive":[32],"visibleLetters":[32],"_lastEmittedLetter":[32],"magIndex":[32],"_isInBounds":[32],"visualLetterIndex":[32],"letterSelected":[32]}]]]], options); | ||
}); |
@@ -5,3 +5,3 @@ 'use strict'; | ||
const index = require('./index-c184af0b.js'); | ||
const index = require('./index-a83d2d61.js'); | ||
@@ -18,3 +18,3 @@ /* | ||
return patchEsm().then(() => { | ||
return index.bootstrapLazy([["index-scrollbar.cjs",[[1,"index-scrollbar",{"letterMagnification":[4,"letter-magnification"],"exactX":[4,"exact-x"],"fontSize":[2,"font-size"],"magnificationSize":[2,"magnification-size"],"alphabet":[16],"validLetters":[16],"navigateOnHover":[4,"navigate-on-hover"],"visibleLetters":[32],"letterIndex":[32],"active":[32],"rendering":[32]}]]]], options); | ||
return index.bootstrapLazy([["index-scrollbar.cjs",[[1,"index-scrollbar",{"alphabet":[16],"overflowDivider":[1,"overflow-divider"],"validLetters":[16],"disableInvalidLetters":[4,"disable-invalid-letters"],"prioritizeHidingInvalidLetters":[4,"prioritize-hiding-invalid-letters"],"letterMagnification":[4,"letter-magnification"],"magnifyDividers":[4,"magnify-dividers"],"magnificationMultiplier":[2,"magnification-multiplier"],"magnificationCurve":[16],"exactX":[4,"exact-x"],"navigateOnHover":[4,"navigate-on-hover"],"letterSpacing":[8,"letter-spacing"],"offsetSizeCheckInterval":[2,"offset-size-check-interval"],"_isComponentActive":[32],"visibleLetters":[32],"_lastEmittedLetter":[32],"magIndex":[32],"_isInBounds":[32],"visualLetterIndex":[32],"letterSelected":[32]}]]]], options); | ||
}); | ||
@@ -21,0 +21,0 @@ }; |
@@ -1,82 +0,150 @@ | ||
import { Component, Host, h, Prop, Event, Element, State } from '@stencil/core'; | ||
import { Component, Host, h, Prop, Event, Element, State, Watch } from '@stencil/core'; | ||
export class IndexScrollbar { | ||
constructor() { | ||
//A custom alphabet to be used instead of the default alphabet. Default is 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' | ||
this.alphabet = [...'ABCDEFGHIJKLMNOPQRSTUVWXYZ']; | ||
//A custom overflow divider. Can be undefined or null if you don't want to use one. Defaults to '·' | ||
this.overflowDivider = '·'; | ||
//Valid letters that are available for the user to select. default is all letters | ||
this.validLetters = this.alphabet; | ||
//Whether or invalid letters should be disabled (greyed out and do not magnify) | ||
this.disableInvalidLetters = false; | ||
//Whether or invalid letters should be disabled (greyed out and do not magnify) | ||
this.prioritizeHidingInvalidLetters = false; | ||
//Whether or not letters should be magnified | ||
this.letterMagnification = true; | ||
//Whether or not overflow diveders should be magnified | ||
this.magnifyDividers = false; | ||
//The maximum that the magnification multiplier can be. Default is 3 | ||
this.magnificationMultiplier = 2; | ||
//Magnification curve accepts an array of numbers between 1 and 0 that represets the curve of magnification starting from magnificaiton multiplier to 1: defaults to [1, 0.9, 0.8, 0.7, 0.6, 0.5, 0.4, 0.3, 0.2, 0.1] | ||
this.magnificationCurve = [1, 0.7, 0.5, 0.3, 0.1]; | ||
//If the scrolling for touch screens in the x direction should be lenient. Default is false | ||
this.exactX = false; | ||
this.fontSize = 16; | ||
this.magnificationSize = 32; | ||
this.alphabet = 'abcdefghijklmnopqrstuvwxyz'.toUpperCase().split(''); | ||
this.validLetters = undefined; | ||
//Whether or not letter change event is emitted on mouse hover. Default is false | ||
this.navigateOnHover = false; | ||
this.active = false; | ||
this.rendering = true; | ||
//Percentage or number in pixels of how far apart the letters are. Defaults to 1.75% | ||
this.letterSpacing = 0; | ||
//This interval can be used for fast, regular size-checks | ||
//Useful, if e.g. a splitter-component resizes the scroll-bar but not the window itself. Set in ms and defaults to 0 (disabled) | ||
this.offsetSizeCheckInterval = 0; | ||
this._lastEmittedActive = false; | ||
this._isComponentActive = false; | ||
this.visibleLetters = []; | ||
//Flag for determining letter under pointer | ||
this._lettersShortened = false; | ||
this._isInBounds = true; | ||
} | ||
onAlphabetChange(value) { | ||
if (!(Array.isArray(value) && value.every(it => typeof it === 'string'))) | ||
throw new Error('alphabet must be a string or an array of strings'); | ||
this.checkVisibleLetters(true); | ||
this.visibleLetters = value; | ||
} | ||
onOverflowDividerChange(value) { | ||
if (!(typeof value === 'string' || value === undefined || value === null)) | ||
throw new Error('overflowDivider must be a string'); | ||
this.checkVisibleLetters(true); | ||
} | ||
onValidLettersChange() { | ||
this.checkVisibleLetters(true); | ||
} | ||
onDisableInvalidLettersChange() { | ||
this.checkVisibleLetters(true); | ||
} | ||
onPrioritizeHidingInvalidLettersChange() { | ||
this.checkVisibleLetters(true); | ||
} | ||
onMagnificationMultiplierChange() { | ||
this.checkVisibleLetters(true); | ||
} | ||
onMagnificationCurveChange(value) { | ||
if (!(Array.isArray(value) && value.every(it => typeof it === 'number' && it >= 0 && it <= 1))) | ||
throw new Error('magnificationCurve must be an array of numbers between 0 and 1'); | ||
} | ||
onExactXChange() { | ||
this._isInBounds = true; | ||
} | ||
onLetterSpacingChange(value) { | ||
if (!(typeof value === 'number' || typeof value === 'string' || value === null)) | ||
throw new Error('letterSpacing must be a number, string or null'); | ||
this.checkVisibleLetters(true); | ||
} | ||
onOffsetSizeCheckIntervalChange(value) { | ||
if (this._offsetSizeCheckIntervalTimer) | ||
clearInterval(this._offsetSizeCheckIntervalTimer); | ||
this.offsetSizeCheckInterval = value; | ||
this.offsetSizeCheckInterval && ((this._offsetSizeCheckIntervalTimer = setInterval(() => this.checkVisibleLetters())), this.offsetSizeCheckInterval); | ||
} | ||
connectedCallback() { | ||
window.addEventListener('resize', this.onResize.bind(this)); | ||
window.addEventListener('resize', this.checkVisibleLetters.bind(this)); | ||
this.visibleLetters = this.alphabet; | ||
} | ||
componentDidLoad() { | ||
this.el.shadowRoot.querySelector('.container').addEventListener('touchstart', this.touchStart.bind(this), { passive: true }); | ||
this.el.shadowRoot.querySelector('.container').addEventListener('touchmove', this.touchMove.bind(this), { passive: true }); | ||
this.el.shadowRoot.querySelector('.container').addEventListener('touchend', this.touchEnd.bind(this), { passive: true }); | ||
this.el.shadowRoot.querySelector('.container').addEventListener('mouseenter', this.mouseEnter.bind(this), { passive: true }); | ||
this.el.shadowRoot.querySelector('.container').addEventListener('mousemove', this.mouseMove.bind(this), { passive: true }); | ||
this.el.shadowRoot.querySelector('.container').addEventListener('mouseleave', this.mouseLeave.bind(this), { passive: true }); | ||
this.el.shadowRoot.querySelector('.container').addEventListener('click', this.click.bind(this)); | ||
// I hate this | ||
setTimeout(() => { | ||
this.interval = setInterval(() => { | ||
var _a; | ||
let height = (_a = this.el.shadowRoot.querySelector('.container')) === null || _a === void 0 ? void 0 : _a.clientHeight; | ||
if (height !== this.height) { | ||
this.height = height; | ||
if (height) { | ||
this.onResize(); | ||
this.rendering = false; | ||
} | ||
else { | ||
this.rendering = true; | ||
} | ||
} | ||
}, 100); | ||
}, 100); | ||
this.alphabetContainer.addEventListener('touchstart', ev => this.focusEvent(ev, 'touchstart'), { passive: true }); | ||
this.alphabetContainer.addEventListener('touchmove', ev => this.focusEvent(ev, 'touchmove'), { passive: true }); | ||
this.alphabetContainer.addEventListener('touchend', () => this.focusEnd(), { passive: true }); | ||
this.alphabetContainer.addEventListener('mouseenter', ev => this.focusEvent(ev, 'mouseenter'), { passive: true }); | ||
this.alphabetContainer.addEventListener('mousemove', ev => this.focusEvent(ev, 'mousemove'), { passive: true }); | ||
this.alphabetContainer.addEventListener('mouseleave', () => this.focusEnd(), { passive: true }); | ||
this.alphabetContainer.addEventListener('click', ev => this.focusEvent(ev, 'click'), { passive: true }); | ||
this.alphabetContainer.addEventListener('mouseenter', () => this.focusEnd(), { passive: true }); | ||
this.checkVisibleLetters(true); | ||
} | ||
disconnectedCallback() { | ||
this.el.shadowRoot.querySelector('.container').removeEventListener('touchstart', this.touchStart.bind(this)); | ||
this.el.shadowRoot.querySelector('.container').removeEventListener('touchmove', this.touchMove.bind(this)); | ||
this.el.shadowRoot.querySelector('.container').removeEventListener('touchend', this.touchEnd.bind(this)); | ||
this.el.shadowRoot.querySelector('.container').removeEventListener('mouseenter', this.mouseEnter.bind(this)); | ||
this.el.shadowRoot.querySelector('.container').removeEventListener('mousemove', this.mouseMove.bind(this)); | ||
this.el.shadowRoot.querySelector('.container').removeEventListener('mouseleave', this.mouseLeave.bind(this)); | ||
this.el.shadowRoot.querySelector('.container').removeEventListener('click', this.click.bind(this)); | ||
window.removeEventListener('resize', this.onResize.bind(this)); | ||
clearInterval(this.interval); | ||
this.alphabetContainer.removeEventListener('touchstart', ev => this.focusEvent(ev, 'touchstart')); | ||
this.alphabetContainer.removeEventListener('touchmove', ev => this.focusEvent(ev, 'touchmove')); | ||
this.alphabetContainer.removeEventListener('touchend', () => this.focusEnd()); | ||
this.alphabetContainer.removeEventListener('mouseenter', ev => this.focusEvent(ev, 'mouseenter')); | ||
this.alphabetContainer.removeEventListener('mousemove', ev => this.focusEvent(ev, 'mousemove')); | ||
this.alphabetContainer.removeEventListener('mouseleave', () => this.focusEnd()); | ||
this.alphabetContainer.removeEventListener('click', ev => this.focusEvent(ev, 'click')); | ||
window.removeEventListener('resize', this.checkVisibleLetters.bind(this)); | ||
clearInterval(this._offsetSizeCheckIntervalTimer); | ||
} | ||
onResize() { | ||
get alphabetContainer() { | ||
return this.el.shadowRoot.querySelector('.container'); | ||
} | ||
checkVisibleLetters(force) { | ||
let height = this.alphabetContainer.clientHeight; | ||
if (!force && height === this._lastHeight) { | ||
return; | ||
} | ||
this._lastHeight = height; | ||
let newAlphabet = this.alphabet; | ||
const height = this.el.shadowRoot.querySelector('.container').clientHeight; | ||
// const newLetterIndex = Math.ceil((this.letterIndex * height) / letterSize); | ||
// this.letterIndex = newLetterIndex; | ||
// this.onLetterIndexChange(newLetterIndex); | ||
const numLetters = this.alphabet.length; | ||
const basicLettersSize = this.fontSize * (numLetters - 5 > 0 ? numLetters - 5 : 0); | ||
let specialLettersSize = this.magnificationSize; | ||
for (let i = 0; i < 5; i++) { | ||
specialLettersSize += this.getMultiplier(i) * this.magnificationSize * 2; | ||
let letterSpacing = 0; | ||
let letterSize = this.stringToNumber(getComputedStyle(this.alphabetContainer).getPropertyValue('font-size')); | ||
if (this.letterMagnification) { | ||
letterSize = letterSize * this.magnificationMultiplier * 0.6; | ||
} | ||
const averageLetterSize = (basicLettersSize + specialLettersSize) / numLetters; | ||
if (height / averageLetterSize < numLetters) { | ||
let numHiddenLetters = numLetters - Math.floor(height / averageLetterSize); | ||
//Calculate actual letter spacing | ||
if (typeof this.letterSpacing === 'number') { | ||
letterSpacing = this.letterSpacing; | ||
} | ||
else if (typeof this.letterSpacing === 'string') { | ||
letterSpacing = this.stringToNumber(this.letterSpacing); | ||
if (this.letterSpacing.endsWith('%')) { | ||
letterSpacing = height * (letterSpacing / 100); | ||
} | ||
} | ||
letterSize = letterSize + letterSpacing; | ||
//Remove invalid letters (if set and necessary) | ||
if (this.prioritizeHidingInvalidLetters && !!this.validLetters && height / letterSize < newAlphabet.length) { | ||
newAlphabet = this.validLetters; | ||
} | ||
//Check if there is enough free space for letters | ||
this._lettersShortened = height / letterSize < newAlphabet.length; | ||
if (this._lettersShortened) { | ||
const numHiddenLetters = newAlphabet.length - Math.floor(height / letterSize); | ||
if (numHiddenLetters === newAlphabet.length) | ||
newAlphabet = []; | ||
//determine how many letters to hide | ||
const hiddenHalves = this.getNumHiddenHalves(numHiddenLetters, numLetters) + 1; | ||
const hiddenHalves = this.getNumHiddenHalves(numHiddenLetters, newAlphabet.length) + 1; | ||
// (this.magnifyDividers || numHiddenLetters > newAlphabet.length - 2 ? 1 : 0); | ||
//split alphabet into two halves | ||
let alphabet1 = this.alphabet.slice(0, Math.ceil(numLetters / 2)); | ||
let alphabet2 = this.alphabet.slice(Math.floor(numLetters / 2)).reverse(); | ||
let alphabet1 = newAlphabet.slice(0, Math.ceil(newAlphabet.length / 2)); | ||
let alphabet2 = newAlphabet.slice(Math.floor(newAlphabet.length / 2)).reverse(); | ||
for (let i = 0; i < hiddenHalves; i++) { | ||
alphabet1 = alphabet1.filter((_, index) => { | ||
return index % 2 === 0; | ||
}); | ||
alphabet2 = alphabet2.filter((_, index) => { | ||
return index % 2 === 0; | ||
}); | ||
alphabet1 = alphabet1.filter((_, i) => i % 2 === 0); | ||
alphabet2 = alphabet2.filter((_, i) => i % 2 === 0); | ||
} | ||
@@ -86,3 +154,4 @@ //insert dots between letters | ||
if (i > 0) { | ||
prev.push('·'); | ||
if (this.overflowDivider) | ||
prev.push(this.overflowDivider); | ||
} | ||
@@ -92,6 +161,6 @@ prev.push(curr); | ||
}, []); | ||
//insert dots between letters | ||
alphabet2 = alphabet2.reduce((prev, curr, i) => { | ||
if (i > 0) { | ||
prev.push('·'); | ||
if (this.overflowDivider) | ||
prev.push(this.overflowDivider); | ||
} | ||
@@ -101,4 +170,4 @@ prev.push(curr); | ||
}, []); | ||
if (this.alphabet.length % 2 === 0) | ||
alphabet1.push('·'); | ||
if (this.alphabet.length % 2 === 0 && this.overflowDivider) | ||
alphabet1.push(this.overflowDivider); | ||
newAlphabet = alphabet1.concat(alphabet2.reverse()); | ||
@@ -114,128 +183,100 @@ } | ||
} | ||
isActive(i) { | ||
return this.letterIndex === i + 1 && this.active; | ||
isValid(letter) { | ||
var _a; | ||
return ((_a = this.validLetters) === null || _a === void 0 ? void 0 : _a.includes(letter)) !== false || letter === this.overflowDivider; | ||
} | ||
getMultiplier(i) { | ||
if (!this.active || !this.letterMagnification || (this.visibleLetters.length > i && this.visibleLetters[i] === '·')) | ||
return 1; | ||
const multiplier = this.magnificationSize / this.fontSize; | ||
if (this.letterIndex === i + 1 && this.active) | ||
return multiplier; | ||
else if (i + 1 === this.letterIndex - 1 || i + 1 === this.letterIndex + 1) | ||
return multiplier * 0.8; | ||
else if (i + 1 === this.letterIndex - 2 || i + 1 === this.letterIndex + 2) | ||
return multiplier * 0.7; | ||
else if (i + 1 === this.letterIndex - 3 || i + 1 === this.letterIndex + 3) | ||
return multiplier * 0.6; | ||
else | ||
return 1; | ||
} | ||
//*** Touch Events ***// | ||
touchStart(event) { | ||
this.scrolling.emit(true); | ||
this.active = true; | ||
this.touchMove(event); | ||
} | ||
//updates on every form of touch | ||
touchMove(event) { | ||
const x = event.touches[0].clientX; | ||
const y = event.touches[0].clientY; | ||
const newLetterIndex = this.getLetterIndexFromCoordinates(x, y); | ||
if (newLetterIndex) | ||
this.onLetterIndexChange(newLetterIndex); | ||
} | ||
touchEnd() { | ||
this.lastLetterIndex = null; | ||
this.scrolling.emit(false); | ||
this.active = false; | ||
} | ||
//*** End Touch Events ***// | ||
//*** Mouse Events ***// | ||
mouseEnter(event) { | ||
if (this.navigateOnHover) { | ||
this.scrolling.emit(true); | ||
focusEvent(event, type) { | ||
var _a, _b, _c, _d; | ||
if (!this._lastEmittedActive) { | ||
this.isActive.emit((this._lastEmittedActive = true)); | ||
} | ||
this.active = true; | ||
this.mouseMove(event); | ||
} | ||
mouseMove(event) { | ||
const x = event.clientX; | ||
const y = event.clientY; | ||
const newLetterIndex = this.getLetterIndexFromCoordinates(x, y); | ||
if (!this.navigateOnHover) | ||
return; | ||
if (newLetterIndex) | ||
this.onLetterIndexChange(newLetterIndex); | ||
} | ||
mouseLeave() { | ||
if (this.navigateOnHover) { | ||
this.scrolling.emit(false); | ||
if (type === 'click') | ||
this._isComponentActive = false; | ||
else if (!this._isComponentActive) | ||
this._isComponentActive = true; | ||
this.setLetterFromCoordinates((_b = (_a = event.touches) === null || _a === void 0 ? void 0 : _a[0].clientX) !== null && _b !== void 0 ? _b : event.clientX, (_d = (_c = event.touches) === null || _c === void 0 ? void 0 : _c[0].clientY) !== null && _d !== void 0 ? _d : event.clientY); | ||
if (this._lastEmittedLetter !== this.letterSelected && (this.navigateOnHover || !type.includes('mouse'))) { | ||
this.letterChange.emit((this._lastEmittedLetter = this.letterSelected)); | ||
} | ||
this.lastLetterIndex = null; | ||
this.active = false; | ||
} | ||
click(event) { | ||
event.preventDefault(); | ||
this.lastLetterIndex = null; | ||
const x = event.clientX; | ||
const y = event.clientY; | ||
const newLetterIndex = this.getLetterIndexFromCoordinates(x, y); | ||
this.onLetterIndexChange(newLetterIndex); | ||
focusEnd() { | ||
this.isActive.emit((this._isComponentActive = this._lastEmittedActive = false)); | ||
} | ||
//*** End Mouse Events ***// | ||
//*** Logic ***// | ||
getLetterIndexFromCoordinates(x, y) { | ||
let letterIndex = null; | ||
const aTop = this.el.shadowRoot.querySelector('.container').getBoundingClientRect().top; | ||
const aHeight = this.el.shadowRoot.querySelector('.container').clientHeight; | ||
const index = Math.ceil(((y - aTop) / aHeight) * this.alphabet.length); | ||
const aRightX = this.el.shadowRoot.querySelector('.container').getBoundingClientRect().right; | ||
const aLeftX = this.el.shadowRoot.querySelector('.container').getBoundingClientRect().left; | ||
if (index <= this.alphabet.length && index > 0) { | ||
if (this.exactX) { | ||
if (x < aRightX && x > aLeftX) { | ||
letterIndex = Math.ceil(index); | ||
} | ||
setLetterFromCoordinates(x, y) { | ||
if (this.exactX) { | ||
const rightX = this.alphabetContainer.getBoundingClientRect().right; | ||
const leftX = this.alphabetContainer.getBoundingClientRect().left; | ||
this._isInBounds = x > leftX && x < rightX; | ||
if (!this._isInBounds) { | ||
this.visualLetterIndex = this.visualLetterIndex = null; | ||
return; | ||
} | ||
} | ||
const height = this.alphabetContainer.clientHeight; | ||
//Letters drew outside the viewport or host padding may cause values outsize height boundries (Usage of min/max) | ||
const top = Math.min(Math.max(0, y - this.alphabetContainer.getBoundingClientRect().top), height); | ||
let topRelative = (top / height) * (this.visibleLetters.length - 1); | ||
const preferNext = Math.round(topRelative) < topRelative; | ||
topRelative = Math.round(topRelative); | ||
this.magIndex = topRelative; | ||
//Set visualLetterIndex to the closest valid letter | ||
this.visualLetterIndex = this.getClosestValidLetterIndex(this.visibleLetters, topRelative, preferNext); | ||
if (this._lettersShortened) { | ||
if (this.validLetters) { | ||
this.letterSelected = this.validLetters[Math.round((top / height) * (this.validLetters.length - 1))]; | ||
} | ||
else { | ||
letterIndex = Math.ceil(index); | ||
this.letterSelected = this.alphabet[this.getClosestValidLetterIndex(this.alphabet, topRelative, preferNext)]; | ||
} | ||
} | ||
if (letterIndex) | ||
this.letterIndex = Math.round(letterIndex * (this.visibleLetters.length / this.alphabet.length)); | ||
return letterIndex; | ||
else { | ||
this.letterSelected = this.visibleLetters[this.visualLetterIndex]; | ||
} | ||
} | ||
//emits the letter change | ||
onLetterIndexChange(value) { | ||
if (!value) | ||
return; | ||
if (this.lastLetterIndex === value) | ||
return; | ||
this.lastLetterIndex = value; | ||
const letter = this.validLetters ? this.getClosestValidLetter(value) : this.alphabet[value - 1]; | ||
this.letterChange.emit(letter); | ||
getClosestValidLetterIndex(alphabet, visualLetterIndex, preferNext) { | ||
const lowercaseAlphabet = alphabet.map(l => l.toLowerCase()); | ||
const lowercaseValidLetters = this.validLetters.map(l => l.toLowerCase()); | ||
const validLettersAsNumbers = lowercaseValidLetters.map(l => lowercaseAlphabet.indexOf(l)); | ||
return validLettersAsNumbers.length > 0 | ||
? validLettersAsNumbers.reduce((prev, curr) => preferNext | ||
? Math.abs(curr - visualLetterIndex) > Math.abs(prev - visualLetterIndex) | ||
? prev | ||
: curr | ||
: Math.abs(curr - visualLetterIndex) < Math.abs(prev - visualLetterIndex) | ||
? curr | ||
: prev) | ||
: null; | ||
} | ||
//gets closest letter | ||
getClosestValidLetter(letterIndex) { | ||
const lowercaseAlphabet = this.alphabet.map(letter => letter.toLowerCase()); | ||
const lowercaseValidLetters = this.validLetters.map(letter => letter.toLowerCase()); | ||
const validLettersAsNumbers = lowercaseValidLetters.map(letter => lowercaseAlphabet.indexOf(letter) + 1); | ||
const closest = validLettersAsNumbers.reduce((prev, curr) => (Math.abs(curr - letterIndex) < Math.abs(prev - letterIndex) ? curr : prev)); | ||
return this.alphabet[closest - 1]; | ||
stringToNumber(value) { | ||
return Number(value === null || value === void 0 ? void 0 : value.match(/[\.\d]+/)[0]); | ||
} | ||
getLetterStyle(index) { | ||
if ((this.magIndex === undefined && this.magIndex === null) || | ||
(!this.magnifyDividers && this.visibleLetters[index] === this.overflowDivider) || | ||
(this.disableInvalidLetters && !this.isValid(this.visibleLetters[index]))) | ||
return {}; | ||
const lettersOnly = this.visibleLetters.filter(l => l !== this.overflowDivider); | ||
const mappedIndex = Math.round((index / this.visibleLetters.length) * lettersOnly.length); | ||
const mappedMagIndex = Math.round((this.magIndex / this.visibleLetters.length) * lettersOnly.length); | ||
let relativeIndex = this.magnifyDividers ? Math.abs(this.magIndex - index) : Math.abs(mappedMagIndex - mappedIndex); | ||
const magnification = relativeIndex < this.magnificationCurve.length - 1 ? this.magnificationCurve[relativeIndex] * (this.magnificationMultiplier - 1) + 1 : 1; | ||
const style = { | ||
transform: `scale(${magnification})`, | ||
zIndex: this.magIndex === index ? 1 : 0, | ||
}; | ||
return this._isInBounds && this._isComponentActive && this.letterMagnification ? style : { transform: 'scale(1)', zIndex: 0 }; | ||
} | ||
render() { | ||
return (h(Host, { style: { opacity: !this.rendering ? '1' : '0' } }, | ||
var _a; | ||
return (h(Host, null, | ||
h("div", { class: { | ||
container: true, | ||
}, style: { | ||
width: this.fontSize + 'px', | ||
} }, this.visibleLetters.map((letter, i) => { | ||
} }, (_a = this.visibleLetters) === null || _a === void 0 ? void 0 : _a.map((letter, i) => { | ||
return (h("div", { key: `${letter}-${i}`, class: { | ||
letter: true, | ||
active: this.isActive(i) && this.letterMagnification, | ||
}, id: this.visibleLetters[i] }, | ||
h("label", { style: { | ||
transform: `translate(-50%, -50%) scale(${this.getMultiplier(i)})`, | ||
fontSize: this.fontSize + 'px', | ||
} }, letter))); | ||
'letter': true, | ||
'letter-disabled': this.disableInvalidLetters && !this.isValid(letter), | ||
}, style: this.getLetterStyle(i), id: this.visibleLetters[i] }, | ||
h("label", null, | ||
" ", | ||
letter))); | ||
})))); | ||
@@ -252,3 +293,57 @@ } | ||
static get properties() { return { | ||
"letterMagnification": { | ||
"alphabet": { | ||
"type": "unknown", | ||
"mutable": false, | ||
"complexType": { | ||
"original": "Array<string>", | ||
"resolved": "string[]", | ||
"references": { | ||
"Array": { | ||
"location": "global" | ||
} | ||
} | ||
}, | ||
"required": false, | ||
"optional": false, | ||
"docs": { | ||
"tags": [], | ||
"text": "" | ||
}, | ||
"defaultValue": "[...'ABCDEFGHIJKLMNOPQRSTUVWXYZ']" | ||
}, | ||
"overflowDivider": { | ||
"type": "string", | ||
"mutable": false, | ||
"complexType": { | ||
"original": "string | undefined | null", | ||
"resolved": "string", | ||
"references": {} | ||
}, | ||
"required": false, | ||
"optional": false, | ||
"docs": { | ||
"tags": [], | ||
"text": "" | ||
}, | ||
"attribute": "overflow-divider", | ||
"reflect": false, | ||
"defaultValue": "'\u00B7'" | ||
}, | ||
"validLetters": { | ||
"type": "unknown", | ||
"mutable": false, | ||
"complexType": { | ||
"original": "string[]", | ||
"resolved": "string[]", | ||
"references": {} | ||
}, | ||
"required": false, | ||
"optional": false, | ||
"docs": { | ||
"tags": [], | ||
"text": "" | ||
}, | ||
"defaultValue": "this.alphabet" | ||
}, | ||
"disableInvalidLetters": { | ||
"type": "boolean", | ||
@@ -267,7 +362,7 @@ "mutable": false, | ||
}, | ||
"attribute": "letter-magnification", | ||
"attribute": "disable-invalid-letters", | ||
"reflect": false, | ||
"defaultValue": "true" | ||
"defaultValue": "false" | ||
}, | ||
"exactX": { | ||
"prioritizeHidingInvalidLetters": { | ||
"type": "boolean", | ||
@@ -286,12 +381,12 @@ "mutable": false, | ||
}, | ||
"attribute": "exact-x", | ||
"attribute": "prioritize-hiding-invalid-letters", | ||
"reflect": false, | ||
"defaultValue": "false" | ||
}, | ||
"fontSize": { | ||
"type": "number", | ||
"letterMagnification": { | ||
"type": "boolean", | ||
"mutable": false, | ||
"complexType": { | ||
"original": "number", | ||
"resolved": "number", | ||
"original": "boolean", | ||
"resolved": "boolean", | ||
"references": {} | ||
@@ -305,7 +400,25 @@ }, | ||
}, | ||
"attribute": "font-size", | ||
"attribute": "letter-magnification", | ||
"reflect": false, | ||
"defaultValue": "16" | ||
"defaultValue": "true" | ||
}, | ||
"magnificationSize": { | ||
"magnifyDividers": { | ||
"type": "boolean", | ||
"mutable": false, | ||
"complexType": { | ||
"original": "boolean", | ||
"resolved": "boolean", | ||
"references": {} | ||
}, | ||
"required": false, | ||
"optional": false, | ||
"docs": { | ||
"tags": [], | ||
"text": "" | ||
}, | ||
"attribute": "magnify-dividers", | ||
"reflect": false, | ||
"defaultValue": "false" | ||
}, | ||
"magnificationMultiplier": { | ||
"type": "number", | ||
@@ -324,12 +437,12 @@ "mutable": false, | ||
}, | ||
"attribute": "magnification-size", | ||
"attribute": "magnification-multiplier", | ||
"reflect": false, | ||
"defaultValue": "32" | ||
"defaultValue": "2" | ||
}, | ||
"alphabet": { | ||
"magnificationCurve": { | ||
"type": "unknown", | ||
"mutable": false, | ||
"complexType": { | ||
"original": "Array<string>", | ||
"resolved": "string[]", | ||
"original": "Array<number>", | ||
"resolved": "number[]", | ||
"references": { | ||
@@ -347,15 +460,11 @@ "Array": { | ||
}, | ||
"defaultValue": "'abcdefghijklmnopqrstuvwxyz'.toUpperCase().split('')" | ||
"defaultValue": "[1, 0.7, 0.5, 0.3, 0.1]" | ||
}, | ||
"validLetters": { | ||
"type": "unknown", | ||
"exactX": { | ||
"type": "boolean", | ||
"mutable": false, | ||
"complexType": { | ||
"original": "Array<string>", | ||
"resolved": "string[]", | ||
"references": { | ||
"Array": { | ||
"location": "global" | ||
} | ||
} | ||
"original": "boolean", | ||
"resolved": "boolean", | ||
"references": {} | ||
}, | ||
@@ -368,3 +477,5 @@ "required": false, | ||
}, | ||
"defaultValue": "undefined" | ||
"attribute": "exact-x", | ||
"reflect": false, | ||
"defaultValue": "false" | ||
}, | ||
@@ -388,9 +499,48 @@ "navigateOnHover": { | ||
"defaultValue": "false" | ||
}, | ||
"letterSpacing": { | ||
"type": "any", | ||
"mutable": false, | ||
"complexType": { | ||
"original": "number | string | null", | ||
"resolved": "number | string", | ||
"references": {} | ||
}, | ||
"required": false, | ||
"optional": false, | ||
"docs": { | ||
"tags": [], | ||
"text": "" | ||
}, | ||
"attribute": "letter-spacing", | ||
"reflect": false, | ||
"defaultValue": "0" | ||
}, | ||
"offsetSizeCheckInterval": { | ||
"type": "number", | ||
"mutable": false, | ||
"complexType": { | ||
"original": "number", | ||
"resolved": "number", | ||
"references": {} | ||
}, | ||
"required": false, | ||
"optional": false, | ||
"docs": { | ||
"tags": [], | ||
"text": "" | ||
}, | ||
"attribute": "offset-size-check-interval", | ||
"reflect": false, | ||
"defaultValue": "0" | ||
} | ||
}; } | ||
static get states() { return { | ||
"_isComponentActive": {}, | ||
"visibleLetters": {}, | ||
"letterIndex": {}, | ||
"active": {}, | ||
"rendering": {} | ||
"_lastEmittedLetter": {}, | ||
"magIndex": {}, | ||
"_isInBounds": {}, | ||
"visualLetterIndex": {}, | ||
"letterSelected": {} | ||
}; } | ||
@@ -413,4 +563,4 @@ static get events() { return [{ | ||
}, { | ||
"method": "scrolling", | ||
"name": "scrolling", | ||
"method": "isActive", | ||
"name": "isActive", | ||
"bubbles": true, | ||
@@ -430,2 +580,33 @@ "cancelable": true, | ||
static get elementRef() { return "el"; } | ||
static get watchers() { return [{ | ||
"propName": "alphabet", | ||
"methodName": "onAlphabetChange" | ||
}, { | ||
"propName": "overflowDivider", | ||
"methodName": "onOverflowDividerChange" | ||
}, { | ||
"propName": "validLetters", | ||
"methodName": "onValidLettersChange" | ||
}, { | ||
"propName": "disableInvalidLetters", | ||
"methodName": "onDisableInvalidLettersChange" | ||
}, { | ||
"propName": "prioritizeHidingInvalidLetters", | ||
"methodName": "onPrioritizeHidingInvalidLettersChange" | ||
}, { | ||
"propName": "magnificationMultiplier", | ||
"methodName": "onMagnificationMultiplierChange" | ||
}, { | ||
"propName": "magnificationCurve", | ||
"methodName": "onMagnificationCurveChange" | ||
}, { | ||
"propName": "exactX", | ||
"methodName": "onExactXChange" | ||
}, { | ||
"propName": "letterSpacing", | ||
"methodName": "onLetterSpacingChange" | ||
}, { | ||
"propName": "offsetSizeCheckInterval", | ||
"methodName": "onOffsetSizeCheckIntervalChange" | ||
}]; } | ||
} |
import { HTMLElement, createEvent, h, Host, proxyCustomElement } from '@stencil/core/internal/client'; | ||
const indexScrollbarCss = ":host{transition:opacity 0.2s ease-in-out}:host .container{height:100%;font-size:70%;display:flex;flex-direction:column;align-items:center;justify-content:space-around}:host .container .letter{position:relative;pointer-events:none}:host .container .letter label{position:absolute;top:50%;left:50%;transform:translate(-50%, -50%);transition:transform 0.15s;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;}:host .container .letter.active label{font-weight:500}"; | ||
const indexScrollbarCss = ":host{top:0;bottom:0;font-size:min(20px, max(12px, 1vh));padding:calc(2 * min(20px, max(12px, 1vh))) 0}:host .container{height:100%;display:flex;flex-direction:column;align-items:center;justify-content:space-between}:host .container.cursor-pointer{cursor:pointer}:host .container .letter{padding:0 20px;position:relative;pointer-events:none;transition:transform 0.2s ease-in-out;transform-origin:60%}:host .container .letter label{position:absolute;top:50%;left:50%;transform:translate(-50%, -50%)}:host .container .letter.letter-disabled{opacity:0.3}:host .container .letter.letter-is-hidden-value{transform-origin:center;transform:scale(2)}"; | ||
@@ -11,81 +11,149 @@ let IndexScrollbar$1 = class extends HTMLElement { | ||
this.letterChange = createEvent(this, "letterChange", 7); | ||
this.scrolling = createEvent(this, "scrolling", 7); | ||
this.isActive = createEvent(this, "isActive", 7); | ||
//A custom alphabet to be used instead of the default alphabet. Default is 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' | ||
this.alphabet = [...'ABCDEFGHIJKLMNOPQRSTUVWXYZ']; | ||
//A custom overflow divider. Can be undefined or null if you don't want to use one. Defaults to '·' | ||
this.overflowDivider = '·'; | ||
//Valid letters that are available for the user to select. default is all letters | ||
this.validLetters = this.alphabet; | ||
//Whether or invalid letters should be disabled (greyed out and do not magnify) | ||
this.disableInvalidLetters = false; | ||
//Whether or invalid letters should be disabled (greyed out and do not magnify) | ||
this.prioritizeHidingInvalidLetters = false; | ||
//Whether or not letters should be magnified | ||
this.letterMagnification = true; | ||
//Whether or not overflow diveders should be magnified | ||
this.magnifyDividers = false; | ||
//The maximum that the magnification multiplier can be. Default is 3 | ||
this.magnificationMultiplier = 2; | ||
//Magnification curve accepts an array of numbers between 1 and 0 that represets the curve of magnification starting from magnificaiton multiplier to 1: defaults to [1, 0.9, 0.8, 0.7, 0.6, 0.5, 0.4, 0.3, 0.2, 0.1] | ||
this.magnificationCurve = [1, 0.7, 0.5, 0.3, 0.1]; | ||
//If the scrolling for touch screens in the x direction should be lenient. Default is false | ||
this.exactX = false; | ||
this.fontSize = 16; | ||
this.magnificationSize = 32; | ||
this.alphabet = 'abcdefghijklmnopqrstuvwxyz'.toUpperCase().split(''); | ||
this.validLetters = undefined; | ||
//Whether or not letter change event is emitted on mouse hover. Default is false | ||
this.navigateOnHover = false; | ||
this.active = false; | ||
this.rendering = true; | ||
//Percentage or number in pixels of how far apart the letters are. Defaults to 1.75% | ||
this.letterSpacing = 0; | ||
//This interval can be used for fast, regular size-checks | ||
//Useful, if e.g. a splitter-component resizes the scroll-bar but not the window itself. Set in ms and defaults to 0 (disabled) | ||
this.offsetSizeCheckInterval = 0; | ||
this._lastEmittedActive = false; | ||
this._isComponentActive = false; | ||
this.visibleLetters = []; | ||
//Flag for determining letter under pointer | ||
this._lettersShortened = false; | ||
this._isInBounds = true; | ||
} | ||
onAlphabetChange(value) { | ||
if (!(Array.isArray(value) && value.every(it => typeof it === 'string'))) | ||
throw new Error('alphabet must be a string or an array of strings'); | ||
this.checkVisibleLetters(true); | ||
this.visibleLetters = value; | ||
} | ||
onOverflowDividerChange(value) { | ||
if (!(typeof value === 'string' || value === undefined || value === null)) | ||
throw new Error('overflowDivider must be a string'); | ||
this.checkVisibleLetters(true); | ||
} | ||
onValidLettersChange() { | ||
this.checkVisibleLetters(true); | ||
} | ||
onDisableInvalidLettersChange() { | ||
this.checkVisibleLetters(true); | ||
} | ||
onPrioritizeHidingInvalidLettersChange() { | ||
this.checkVisibleLetters(true); | ||
} | ||
onMagnificationMultiplierChange() { | ||
this.checkVisibleLetters(true); | ||
} | ||
onMagnificationCurveChange(value) { | ||
if (!(Array.isArray(value) && value.every(it => typeof it === 'number' && it >= 0 && it <= 1))) | ||
throw new Error('magnificationCurve must be an array of numbers between 0 and 1'); | ||
} | ||
onExactXChange() { | ||
this._isInBounds = true; | ||
} | ||
onLetterSpacingChange(value) { | ||
if (!(typeof value === 'number' || typeof value === 'string' || value === null)) | ||
throw new Error('letterSpacing must be a number, string or null'); | ||
this.checkVisibleLetters(true); | ||
} | ||
onOffsetSizeCheckIntervalChange(value) { | ||
if (this._offsetSizeCheckIntervalTimer) | ||
clearInterval(this._offsetSizeCheckIntervalTimer); | ||
this.offsetSizeCheckInterval = value; | ||
this.offsetSizeCheckInterval && ((this._offsetSizeCheckIntervalTimer = setInterval(() => this.checkVisibleLetters())), this.offsetSizeCheckInterval); | ||
} | ||
connectedCallback() { | ||
window.addEventListener('resize', this.onResize.bind(this)); | ||
window.addEventListener('resize', this.checkVisibleLetters.bind(this)); | ||
this.visibleLetters = this.alphabet; | ||
} | ||
componentDidLoad() { | ||
this.el.shadowRoot.querySelector('.container').addEventListener('touchstart', this.touchStart.bind(this), { passive: true }); | ||
this.el.shadowRoot.querySelector('.container').addEventListener('touchmove', this.touchMove.bind(this), { passive: true }); | ||
this.el.shadowRoot.querySelector('.container').addEventListener('touchend', this.touchEnd.bind(this), { passive: true }); | ||
this.el.shadowRoot.querySelector('.container').addEventListener('mouseenter', this.mouseEnter.bind(this), { passive: true }); | ||
this.el.shadowRoot.querySelector('.container').addEventListener('mousemove', this.mouseMove.bind(this), { passive: true }); | ||
this.el.shadowRoot.querySelector('.container').addEventListener('mouseleave', this.mouseLeave.bind(this), { passive: true }); | ||
this.el.shadowRoot.querySelector('.container').addEventListener('click', this.click.bind(this)); | ||
// I hate this | ||
setTimeout(() => { | ||
this.interval = setInterval(() => { | ||
var _a; | ||
let height = (_a = this.el.shadowRoot.querySelector('.container')) === null || _a === void 0 ? void 0 : _a.clientHeight; | ||
if (height !== this.height) { | ||
this.height = height; | ||
if (height) { | ||
this.onResize(); | ||
this.rendering = false; | ||
} | ||
else { | ||
this.rendering = true; | ||
} | ||
} | ||
}, 100); | ||
}, 100); | ||
this.alphabetContainer.addEventListener('touchstart', ev => this.focusEvent(ev, 'touchstart'), { passive: true }); | ||
this.alphabetContainer.addEventListener('touchmove', ev => this.focusEvent(ev, 'touchmove'), { passive: true }); | ||
this.alphabetContainer.addEventListener('touchend', () => this.focusEnd(), { passive: true }); | ||
this.alphabetContainer.addEventListener('mouseenter', ev => this.focusEvent(ev, 'mouseenter'), { passive: true }); | ||
this.alphabetContainer.addEventListener('mousemove', ev => this.focusEvent(ev, 'mousemove'), { passive: true }); | ||
this.alphabetContainer.addEventListener('mouseleave', () => this.focusEnd(), { passive: true }); | ||
this.alphabetContainer.addEventListener('click', ev => this.focusEvent(ev, 'click'), { passive: true }); | ||
this.alphabetContainer.addEventListener('mouseenter', () => this.focusEnd(), { passive: true }); | ||
this.checkVisibleLetters(true); | ||
} | ||
disconnectedCallback() { | ||
this.el.shadowRoot.querySelector('.container').removeEventListener('touchstart', this.touchStart.bind(this)); | ||
this.el.shadowRoot.querySelector('.container').removeEventListener('touchmove', this.touchMove.bind(this)); | ||
this.el.shadowRoot.querySelector('.container').removeEventListener('touchend', this.touchEnd.bind(this)); | ||
this.el.shadowRoot.querySelector('.container').removeEventListener('mouseenter', this.mouseEnter.bind(this)); | ||
this.el.shadowRoot.querySelector('.container').removeEventListener('mousemove', this.mouseMove.bind(this)); | ||
this.el.shadowRoot.querySelector('.container').removeEventListener('mouseleave', this.mouseLeave.bind(this)); | ||
this.el.shadowRoot.querySelector('.container').removeEventListener('click', this.click.bind(this)); | ||
window.removeEventListener('resize', this.onResize.bind(this)); | ||
clearInterval(this.interval); | ||
this.alphabetContainer.removeEventListener('touchstart', ev => this.focusEvent(ev, 'touchstart')); | ||
this.alphabetContainer.removeEventListener('touchmove', ev => this.focusEvent(ev, 'touchmove')); | ||
this.alphabetContainer.removeEventListener('touchend', () => this.focusEnd()); | ||
this.alphabetContainer.removeEventListener('mouseenter', ev => this.focusEvent(ev, 'mouseenter')); | ||
this.alphabetContainer.removeEventListener('mousemove', ev => this.focusEvent(ev, 'mousemove')); | ||
this.alphabetContainer.removeEventListener('mouseleave', () => this.focusEnd()); | ||
this.alphabetContainer.removeEventListener('click', ev => this.focusEvent(ev, 'click')); | ||
window.removeEventListener('resize', this.checkVisibleLetters.bind(this)); | ||
clearInterval(this._offsetSizeCheckIntervalTimer); | ||
} | ||
onResize() { | ||
get alphabetContainer() { | ||
return this.el.shadowRoot.querySelector('.container'); | ||
} | ||
checkVisibleLetters(force) { | ||
let height = this.alphabetContainer.clientHeight; | ||
if (!force && height === this._lastHeight) { | ||
return; | ||
} | ||
this._lastHeight = height; | ||
let newAlphabet = this.alphabet; | ||
const height = this.el.shadowRoot.querySelector('.container').clientHeight; | ||
// const newLetterIndex = Math.ceil((this.letterIndex * height) / letterSize); | ||
// this.letterIndex = newLetterIndex; | ||
// this.onLetterIndexChange(newLetterIndex); | ||
const numLetters = this.alphabet.length; | ||
const basicLettersSize = this.fontSize * (numLetters - 5 > 0 ? numLetters - 5 : 0); | ||
let specialLettersSize = this.magnificationSize; | ||
for (let i = 0; i < 5; i++) { | ||
specialLettersSize += this.getMultiplier(i) * this.magnificationSize * 2; | ||
let letterSpacing = 0; | ||
let letterSize = this.stringToNumber(getComputedStyle(this.alphabetContainer).getPropertyValue('font-size')); | ||
if (this.letterMagnification) { | ||
letterSize = letterSize * this.magnificationMultiplier * 0.6; | ||
} | ||
const averageLetterSize = (basicLettersSize + specialLettersSize) / numLetters; | ||
if (height / averageLetterSize < numLetters) { | ||
let numHiddenLetters = numLetters - Math.floor(height / averageLetterSize); | ||
//Calculate actual letter spacing | ||
if (typeof this.letterSpacing === 'number') { | ||
letterSpacing = this.letterSpacing; | ||
} | ||
else if (typeof this.letterSpacing === 'string') { | ||
letterSpacing = this.stringToNumber(this.letterSpacing); | ||
if (this.letterSpacing.endsWith('%')) { | ||
letterSpacing = height * (letterSpacing / 100); | ||
} | ||
} | ||
letterSize = letterSize + letterSpacing; | ||
//Remove invalid letters (if set and necessary) | ||
if (this.prioritizeHidingInvalidLetters && !!this.validLetters && height / letterSize < newAlphabet.length) { | ||
newAlphabet = this.validLetters; | ||
} | ||
//Check if there is enough free space for letters | ||
this._lettersShortened = height / letterSize < newAlphabet.length; | ||
if (this._lettersShortened) { | ||
const numHiddenLetters = newAlphabet.length - Math.floor(height / letterSize); | ||
if (numHiddenLetters === newAlphabet.length) | ||
newAlphabet = []; | ||
//determine how many letters to hide | ||
const hiddenHalves = this.getNumHiddenHalves(numHiddenLetters, numLetters) + 1; | ||
const hiddenHalves = this.getNumHiddenHalves(numHiddenLetters, newAlphabet.length) + 1; | ||
// (this.magnifyDividers || numHiddenLetters > newAlphabet.length - 2 ? 1 : 0); | ||
//split alphabet into two halves | ||
let alphabet1 = this.alphabet.slice(0, Math.ceil(numLetters / 2)); | ||
let alphabet2 = this.alphabet.slice(Math.floor(numLetters / 2)).reverse(); | ||
let alphabet1 = newAlphabet.slice(0, Math.ceil(newAlphabet.length / 2)); | ||
let alphabet2 = newAlphabet.slice(Math.floor(newAlphabet.length / 2)).reverse(); | ||
for (let i = 0; i < hiddenHalves; i++) { | ||
alphabet1 = alphabet1.filter((_, index) => { | ||
return index % 2 === 0; | ||
}); | ||
alphabet2 = alphabet2.filter((_, index) => { | ||
return index % 2 === 0; | ||
}); | ||
alphabet1 = alphabet1.filter((_, i) => i % 2 === 0); | ||
alphabet2 = alphabet2.filter((_, i) => i % 2 === 0); | ||
} | ||
@@ -95,3 +163,4 @@ //insert dots between letters | ||
if (i > 0) { | ||
prev.push('·'); | ||
if (this.overflowDivider) | ||
prev.push(this.overflowDivider); | ||
} | ||
@@ -101,6 +170,6 @@ prev.push(curr); | ||
}, []); | ||
//insert dots between letters | ||
alphabet2 = alphabet2.reduce((prev, curr, i) => { | ||
if (i > 0) { | ||
prev.push('·'); | ||
if (this.overflowDivider) | ||
prev.push(this.overflowDivider); | ||
} | ||
@@ -110,4 +179,4 @@ prev.push(curr); | ||
}, []); | ||
if (this.alphabet.length % 2 === 0) | ||
alphabet1.push('·'); | ||
if (this.alphabet.length % 2 === 0 && this.overflowDivider) | ||
alphabet1.push(this.overflowDivider); | ||
newAlphabet = alphabet1.concat(alphabet2.reverse()); | ||
@@ -123,143 +192,134 @@ } | ||
} | ||
isActive(i) { | ||
return this.letterIndex === i + 1 && this.active; | ||
isValid(letter) { | ||
var _a; | ||
return ((_a = this.validLetters) === null || _a === void 0 ? void 0 : _a.includes(letter)) !== false || letter === this.overflowDivider; | ||
} | ||
getMultiplier(i) { | ||
if (!this.active || !this.letterMagnification || (this.visibleLetters.length > i && this.visibleLetters[i] === '·')) | ||
return 1; | ||
const multiplier = this.magnificationSize / this.fontSize; | ||
if (this.letterIndex === i + 1 && this.active) | ||
return multiplier; | ||
else if (i + 1 === this.letterIndex - 1 || i + 1 === this.letterIndex + 1) | ||
return multiplier * 0.8; | ||
else if (i + 1 === this.letterIndex - 2 || i + 1 === this.letterIndex + 2) | ||
return multiplier * 0.7; | ||
else if (i + 1 === this.letterIndex - 3 || i + 1 === this.letterIndex + 3) | ||
return multiplier * 0.6; | ||
else | ||
return 1; | ||
} | ||
//*** Touch Events ***// | ||
touchStart(event) { | ||
this.scrolling.emit(true); | ||
this.active = true; | ||
this.touchMove(event); | ||
} | ||
//updates on every form of touch | ||
touchMove(event) { | ||
const x = event.touches[0].clientX; | ||
const y = event.touches[0].clientY; | ||
const newLetterIndex = this.getLetterIndexFromCoordinates(x, y); | ||
if (newLetterIndex) | ||
this.onLetterIndexChange(newLetterIndex); | ||
} | ||
touchEnd() { | ||
this.lastLetterIndex = null; | ||
this.scrolling.emit(false); | ||
this.active = false; | ||
} | ||
//*** End Touch Events ***// | ||
//*** Mouse Events ***// | ||
mouseEnter(event) { | ||
if (this.navigateOnHover) { | ||
this.scrolling.emit(true); | ||
focusEvent(event, type) { | ||
var _a, _b, _c, _d; | ||
if (!this._lastEmittedActive) { | ||
this.isActive.emit((this._lastEmittedActive = true)); | ||
} | ||
this.active = true; | ||
this.mouseMove(event); | ||
} | ||
mouseMove(event) { | ||
const x = event.clientX; | ||
const y = event.clientY; | ||
const newLetterIndex = this.getLetterIndexFromCoordinates(x, y); | ||
if (!this.navigateOnHover) | ||
return; | ||
if (newLetterIndex) | ||
this.onLetterIndexChange(newLetterIndex); | ||
} | ||
mouseLeave() { | ||
if (this.navigateOnHover) { | ||
this.scrolling.emit(false); | ||
if (type === 'click') | ||
this._isComponentActive = false; | ||
else if (!this._isComponentActive) | ||
this._isComponentActive = true; | ||
this.setLetterFromCoordinates((_b = (_a = event.touches) === null || _a === void 0 ? void 0 : _a[0].clientX) !== null && _b !== void 0 ? _b : event.clientX, (_d = (_c = event.touches) === null || _c === void 0 ? void 0 : _c[0].clientY) !== null && _d !== void 0 ? _d : event.clientY); | ||
if (this._lastEmittedLetter !== this.letterSelected && (this.navigateOnHover || !type.includes('mouse'))) { | ||
this.letterChange.emit((this._lastEmittedLetter = this.letterSelected)); | ||
} | ||
this.lastLetterIndex = null; | ||
this.active = false; | ||
} | ||
click(event) { | ||
event.preventDefault(); | ||
this.lastLetterIndex = null; | ||
const x = event.clientX; | ||
const y = event.clientY; | ||
const newLetterIndex = this.getLetterIndexFromCoordinates(x, y); | ||
this.onLetterIndexChange(newLetterIndex); | ||
focusEnd() { | ||
this.isActive.emit((this._isComponentActive = this._lastEmittedActive = false)); | ||
} | ||
//*** End Mouse Events ***// | ||
//*** Logic ***// | ||
getLetterIndexFromCoordinates(x, y) { | ||
let letterIndex = null; | ||
const aTop = this.el.shadowRoot.querySelector('.container').getBoundingClientRect().top; | ||
const aHeight = this.el.shadowRoot.querySelector('.container').clientHeight; | ||
const index = Math.ceil(((y - aTop) / aHeight) * this.alphabet.length); | ||
const aRightX = this.el.shadowRoot.querySelector('.container').getBoundingClientRect().right; | ||
const aLeftX = this.el.shadowRoot.querySelector('.container').getBoundingClientRect().left; | ||
if (index <= this.alphabet.length && index > 0) { | ||
if (this.exactX) { | ||
if (x < aRightX && x > aLeftX) { | ||
letterIndex = Math.ceil(index); | ||
} | ||
setLetterFromCoordinates(x, y) { | ||
if (this.exactX) { | ||
const rightX = this.alphabetContainer.getBoundingClientRect().right; | ||
const leftX = this.alphabetContainer.getBoundingClientRect().left; | ||
this._isInBounds = x > leftX && x < rightX; | ||
if (!this._isInBounds) { | ||
this.visualLetterIndex = this.visualLetterIndex = null; | ||
return; | ||
} | ||
} | ||
const height = this.alphabetContainer.clientHeight; | ||
//Letters drew outside the viewport or host padding may cause values outsize height boundries (Usage of min/max) | ||
const top = Math.min(Math.max(0, y - this.alphabetContainer.getBoundingClientRect().top), height); | ||
let topRelative = (top / height) * (this.visibleLetters.length - 1); | ||
const preferNext = Math.round(topRelative) < topRelative; | ||
topRelative = Math.round(topRelative); | ||
this.magIndex = topRelative; | ||
//Set visualLetterIndex to the closest valid letter | ||
this.visualLetterIndex = this.getClosestValidLetterIndex(this.visibleLetters, topRelative, preferNext); | ||
if (this._lettersShortened) { | ||
if (this.validLetters) { | ||
this.letterSelected = this.validLetters[Math.round((top / height) * (this.validLetters.length - 1))]; | ||
} | ||
else { | ||
letterIndex = Math.ceil(index); | ||
this.letterSelected = this.alphabet[this.getClosestValidLetterIndex(this.alphabet, topRelative, preferNext)]; | ||
} | ||
} | ||
if (letterIndex) | ||
this.letterIndex = Math.round(letterIndex * (this.visibleLetters.length / this.alphabet.length)); | ||
return letterIndex; | ||
else { | ||
this.letterSelected = this.visibleLetters[this.visualLetterIndex]; | ||
} | ||
} | ||
//emits the letter change | ||
onLetterIndexChange(value) { | ||
if (!value) | ||
return; | ||
if (this.lastLetterIndex === value) | ||
return; | ||
this.lastLetterIndex = value; | ||
const letter = this.validLetters ? this.getClosestValidLetter(value) : this.alphabet[value - 1]; | ||
this.letterChange.emit(letter); | ||
getClosestValidLetterIndex(alphabet, visualLetterIndex, preferNext) { | ||
const lowercaseAlphabet = alphabet.map(l => l.toLowerCase()); | ||
const lowercaseValidLetters = this.validLetters.map(l => l.toLowerCase()); | ||
const validLettersAsNumbers = lowercaseValidLetters.map(l => lowercaseAlphabet.indexOf(l)); | ||
return validLettersAsNumbers.length > 0 | ||
? validLettersAsNumbers.reduce((prev, curr) => preferNext | ||
? Math.abs(curr - visualLetterIndex) > Math.abs(prev - visualLetterIndex) | ||
? prev | ||
: curr | ||
: Math.abs(curr - visualLetterIndex) < Math.abs(prev - visualLetterIndex) | ||
? curr | ||
: prev) | ||
: null; | ||
} | ||
//gets closest letter | ||
getClosestValidLetter(letterIndex) { | ||
const lowercaseAlphabet = this.alphabet.map(letter => letter.toLowerCase()); | ||
const lowercaseValidLetters = this.validLetters.map(letter => letter.toLowerCase()); | ||
const validLettersAsNumbers = lowercaseValidLetters.map(letter => lowercaseAlphabet.indexOf(letter) + 1); | ||
const closest = validLettersAsNumbers.reduce((prev, curr) => (Math.abs(curr - letterIndex) < Math.abs(prev - letterIndex) ? curr : prev)); | ||
return this.alphabet[closest - 1]; | ||
stringToNumber(value) { | ||
return Number(value === null || value === void 0 ? void 0 : value.match(/[\.\d]+/)[0]); | ||
} | ||
getLetterStyle(index) { | ||
if ((this.magIndex === undefined && this.magIndex === null) || | ||
(!this.magnifyDividers && this.visibleLetters[index] === this.overflowDivider) || | ||
(this.disableInvalidLetters && !this.isValid(this.visibleLetters[index]))) | ||
return {}; | ||
const lettersOnly = this.visibleLetters.filter(l => l !== this.overflowDivider); | ||
const mappedIndex = Math.round((index / this.visibleLetters.length) * lettersOnly.length); | ||
const mappedMagIndex = Math.round((this.magIndex / this.visibleLetters.length) * lettersOnly.length); | ||
let relativeIndex = this.magnifyDividers ? Math.abs(this.magIndex - index) : Math.abs(mappedMagIndex - mappedIndex); | ||
const magnification = relativeIndex < this.magnificationCurve.length - 1 ? this.magnificationCurve[relativeIndex] * (this.magnificationMultiplier - 1) + 1 : 1; | ||
const style = { | ||
transform: `scale(${magnification})`, | ||
zIndex: this.magIndex === index ? 1 : 0, | ||
}; | ||
return this._isInBounds && this._isComponentActive && this.letterMagnification ? style : { transform: 'scale(1)', zIndex: 0 }; | ||
} | ||
render() { | ||
return (h(Host, { style: { opacity: !this.rendering ? '1' : '0' } }, h("div", { class: { | ||
var _a; | ||
return (h(Host, null, h("div", { class: { | ||
container: true, | ||
}, style: { | ||
width: this.fontSize + 'px', | ||
} }, this.visibleLetters.map((letter, i) => { | ||
} }, (_a = this.visibleLetters) === null || _a === void 0 ? void 0 : _a.map((letter, i) => { | ||
return (h("div", { key: `${letter}-${i}`, class: { | ||
letter: true, | ||
active: this.isActive(i) && this.letterMagnification, | ||
}, id: this.visibleLetters[i] }, h("label", { style: { | ||
transform: `translate(-50%, -50%) scale(${this.getMultiplier(i)})`, | ||
fontSize: this.fontSize + 'px', | ||
} }, letter))); | ||
'letter': true, | ||
'letter-disabled': this.disableInvalidLetters && !this.isValid(letter), | ||
}, style: this.getLetterStyle(i), id: this.visibleLetters[i] }, h("label", null, " ", letter))); | ||
})))); | ||
} | ||
get el() { return this; } | ||
static get watchers() { return { | ||
"alphabet": ["onAlphabetChange"], | ||
"overflowDivider": ["onOverflowDividerChange"], | ||
"validLetters": ["onValidLettersChange"], | ||
"disableInvalidLetters": ["onDisableInvalidLettersChange"], | ||
"prioritizeHidingInvalidLetters": ["onPrioritizeHidingInvalidLettersChange"], | ||
"magnificationMultiplier": ["onMagnificationMultiplierChange"], | ||
"magnificationCurve": ["onMagnificationCurveChange"], | ||
"exactX": ["onExactXChange"], | ||
"letterSpacing": ["onLetterSpacingChange"], | ||
"offsetSizeCheckInterval": ["onOffsetSizeCheckIntervalChange"] | ||
}; } | ||
static get style() { return indexScrollbarCss; } | ||
}; | ||
IndexScrollbar$1 = /*@__PURE__*/ proxyCustomElement(IndexScrollbar$1, [1, "index-scrollbar", { | ||
"alphabet": [16], | ||
"overflowDivider": [1, "overflow-divider"], | ||
"validLetters": [16], | ||
"disableInvalidLetters": [4, "disable-invalid-letters"], | ||
"prioritizeHidingInvalidLetters": [4, "prioritize-hiding-invalid-letters"], | ||
"letterMagnification": [4, "letter-magnification"], | ||
"magnifyDividers": [4, "magnify-dividers"], | ||
"magnificationMultiplier": [2, "magnification-multiplier"], | ||
"magnificationCurve": [16], | ||
"exactX": [4, "exact-x"], | ||
"fontSize": [2, "font-size"], | ||
"magnificationSize": [2, "magnification-size"], | ||
"alphabet": [16], | ||
"validLetters": [16], | ||
"navigateOnHover": [4, "navigate-on-hover"], | ||
"letterSpacing": [8, "letter-spacing"], | ||
"offsetSizeCheckInterval": [2, "offset-size-check-interval"], | ||
"_isComponentActive": [32], | ||
"visibleLetters": [32], | ||
"letterIndex": [32], | ||
"active": [32], | ||
"rendering": [32] | ||
"_lastEmittedLetter": [32], | ||
"magIndex": [32], | ||
"_isInBounds": [32], | ||
"visualLetterIndex": [32], | ||
"letterSelected": [32] | ||
}]); | ||
@@ -266,0 +326,0 @@ function defineCustomElement$1() { |
@@ -1,4 +0,4 @@ | ||
import { r as registerInstance, c as createEvent, h, H as Host, g as getElement } from './index-b388e836.js'; | ||
import { r as registerInstance, c as createEvent, h, H as Host, g as getElement } from './index-5f889f85.js'; | ||
const indexScrollbarCss = ":host{transition:opacity 0.2s ease-in-out}:host .container{height:100%;font-size:70%;display:flex;flex-direction:column;align-items:center;justify-content:space-around}:host .container .letter{position:relative;pointer-events:none}:host .container .letter label{position:absolute;top:50%;left:50%;transform:translate(-50%, -50%);transition:transform 0.15s;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;}:host .container .letter.active label{font-weight:500}"; | ||
const indexScrollbarCss = ":host{top:0;bottom:0;font-size:min(20px, max(12px, 1vh));padding:calc(2 * min(20px, max(12px, 1vh))) 0}:host .container{height:100%;display:flex;flex-direction:column;align-items:center;justify-content:space-between}:host .container.cursor-pointer{cursor:pointer}:host .container .letter{padding:0 20px;position:relative;pointer-events:none;transition:transform 0.2s ease-in-out;transform-origin:60%}:host .container .letter label{position:absolute;top:50%;left:50%;transform:translate(-50%, -50%)}:host .container .letter.letter-disabled{opacity:0.3}:host .container .letter.letter-is-hidden-value{transform-origin:center;transform:scale(2)}"; | ||
@@ -9,81 +9,149 @@ let IndexScrollbar = class { | ||
this.letterChange = createEvent(this, "letterChange", 7); | ||
this.scrolling = createEvent(this, "scrolling", 7); | ||
this.isActive = createEvent(this, "isActive", 7); | ||
//A custom alphabet to be used instead of the default alphabet. Default is 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' | ||
this.alphabet = [...'ABCDEFGHIJKLMNOPQRSTUVWXYZ']; | ||
//A custom overflow divider. Can be undefined or null if you don't want to use one. Defaults to '·' | ||
this.overflowDivider = '·'; | ||
//Valid letters that are available for the user to select. default is all letters | ||
this.validLetters = this.alphabet; | ||
//Whether or invalid letters should be disabled (greyed out and do not magnify) | ||
this.disableInvalidLetters = false; | ||
//Whether or invalid letters should be disabled (greyed out and do not magnify) | ||
this.prioritizeHidingInvalidLetters = false; | ||
//Whether or not letters should be magnified | ||
this.letterMagnification = true; | ||
//Whether or not overflow diveders should be magnified | ||
this.magnifyDividers = false; | ||
//The maximum that the magnification multiplier can be. Default is 3 | ||
this.magnificationMultiplier = 2; | ||
//Magnification curve accepts an array of numbers between 1 and 0 that represets the curve of magnification starting from magnificaiton multiplier to 1: defaults to [1, 0.9, 0.8, 0.7, 0.6, 0.5, 0.4, 0.3, 0.2, 0.1] | ||
this.magnificationCurve = [1, 0.7, 0.5, 0.3, 0.1]; | ||
//If the scrolling for touch screens in the x direction should be lenient. Default is false | ||
this.exactX = false; | ||
this.fontSize = 16; | ||
this.magnificationSize = 32; | ||
this.alphabet = 'abcdefghijklmnopqrstuvwxyz'.toUpperCase().split(''); | ||
this.validLetters = undefined; | ||
//Whether or not letter change event is emitted on mouse hover. Default is false | ||
this.navigateOnHover = false; | ||
this.active = false; | ||
this.rendering = true; | ||
//Percentage or number in pixels of how far apart the letters are. Defaults to 1.75% | ||
this.letterSpacing = 0; | ||
//This interval can be used for fast, regular size-checks | ||
//Useful, if e.g. a splitter-component resizes the scroll-bar but not the window itself. Set in ms and defaults to 0 (disabled) | ||
this.offsetSizeCheckInterval = 0; | ||
this._lastEmittedActive = false; | ||
this._isComponentActive = false; | ||
this.visibleLetters = []; | ||
//Flag for determining letter under pointer | ||
this._lettersShortened = false; | ||
this._isInBounds = true; | ||
} | ||
onAlphabetChange(value) { | ||
if (!(Array.isArray(value) && value.every(it => typeof it === 'string'))) | ||
throw new Error('alphabet must be a string or an array of strings'); | ||
this.checkVisibleLetters(true); | ||
this.visibleLetters = value; | ||
} | ||
onOverflowDividerChange(value) { | ||
if (!(typeof value === 'string' || value === undefined || value === null)) | ||
throw new Error('overflowDivider must be a string'); | ||
this.checkVisibleLetters(true); | ||
} | ||
onValidLettersChange() { | ||
this.checkVisibleLetters(true); | ||
} | ||
onDisableInvalidLettersChange() { | ||
this.checkVisibleLetters(true); | ||
} | ||
onPrioritizeHidingInvalidLettersChange() { | ||
this.checkVisibleLetters(true); | ||
} | ||
onMagnificationMultiplierChange() { | ||
this.checkVisibleLetters(true); | ||
} | ||
onMagnificationCurveChange(value) { | ||
if (!(Array.isArray(value) && value.every(it => typeof it === 'number' && it >= 0 && it <= 1))) | ||
throw new Error('magnificationCurve must be an array of numbers between 0 and 1'); | ||
} | ||
onExactXChange() { | ||
this._isInBounds = true; | ||
} | ||
onLetterSpacingChange(value) { | ||
if (!(typeof value === 'number' || typeof value === 'string' || value === null)) | ||
throw new Error('letterSpacing must be a number, string or null'); | ||
this.checkVisibleLetters(true); | ||
} | ||
onOffsetSizeCheckIntervalChange(value) { | ||
if (this._offsetSizeCheckIntervalTimer) | ||
clearInterval(this._offsetSizeCheckIntervalTimer); | ||
this.offsetSizeCheckInterval = value; | ||
this.offsetSizeCheckInterval && ((this._offsetSizeCheckIntervalTimer = setInterval(() => this.checkVisibleLetters())), this.offsetSizeCheckInterval); | ||
} | ||
connectedCallback() { | ||
window.addEventListener('resize', this.onResize.bind(this)); | ||
window.addEventListener('resize', this.checkVisibleLetters.bind(this)); | ||
this.visibleLetters = this.alphabet; | ||
} | ||
componentDidLoad() { | ||
this.el.shadowRoot.querySelector('.container').addEventListener('touchstart', this.touchStart.bind(this), { passive: true }); | ||
this.el.shadowRoot.querySelector('.container').addEventListener('touchmove', this.touchMove.bind(this), { passive: true }); | ||
this.el.shadowRoot.querySelector('.container').addEventListener('touchend', this.touchEnd.bind(this), { passive: true }); | ||
this.el.shadowRoot.querySelector('.container').addEventListener('mouseenter', this.mouseEnter.bind(this), { passive: true }); | ||
this.el.shadowRoot.querySelector('.container').addEventListener('mousemove', this.mouseMove.bind(this), { passive: true }); | ||
this.el.shadowRoot.querySelector('.container').addEventListener('mouseleave', this.mouseLeave.bind(this), { passive: true }); | ||
this.el.shadowRoot.querySelector('.container').addEventListener('click', this.click.bind(this)); | ||
// I hate this | ||
setTimeout(() => { | ||
this.interval = setInterval(() => { | ||
var _a; | ||
let height = (_a = this.el.shadowRoot.querySelector('.container')) === null || _a === void 0 ? void 0 : _a.clientHeight; | ||
if (height !== this.height) { | ||
this.height = height; | ||
if (height) { | ||
this.onResize(); | ||
this.rendering = false; | ||
} | ||
else { | ||
this.rendering = true; | ||
} | ||
} | ||
}, 100); | ||
}, 100); | ||
this.alphabetContainer.addEventListener('touchstart', ev => this.focusEvent(ev, 'touchstart'), { passive: true }); | ||
this.alphabetContainer.addEventListener('touchmove', ev => this.focusEvent(ev, 'touchmove'), { passive: true }); | ||
this.alphabetContainer.addEventListener('touchend', () => this.focusEnd(), { passive: true }); | ||
this.alphabetContainer.addEventListener('mouseenter', ev => this.focusEvent(ev, 'mouseenter'), { passive: true }); | ||
this.alphabetContainer.addEventListener('mousemove', ev => this.focusEvent(ev, 'mousemove'), { passive: true }); | ||
this.alphabetContainer.addEventListener('mouseleave', () => this.focusEnd(), { passive: true }); | ||
this.alphabetContainer.addEventListener('click', ev => this.focusEvent(ev, 'click'), { passive: true }); | ||
this.alphabetContainer.addEventListener('mouseenter', () => this.focusEnd(), { passive: true }); | ||
this.checkVisibleLetters(true); | ||
} | ||
disconnectedCallback() { | ||
this.el.shadowRoot.querySelector('.container').removeEventListener('touchstart', this.touchStart.bind(this)); | ||
this.el.shadowRoot.querySelector('.container').removeEventListener('touchmove', this.touchMove.bind(this)); | ||
this.el.shadowRoot.querySelector('.container').removeEventListener('touchend', this.touchEnd.bind(this)); | ||
this.el.shadowRoot.querySelector('.container').removeEventListener('mouseenter', this.mouseEnter.bind(this)); | ||
this.el.shadowRoot.querySelector('.container').removeEventListener('mousemove', this.mouseMove.bind(this)); | ||
this.el.shadowRoot.querySelector('.container').removeEventListener('mouseleave', this.mouseLeave.bind(this)); | ||
this.el.shadowRoot.querySelector('.container').removeEventListener('click', this.click.bind(this)); | ||
window.removeEventListener('resize', this.onResize.bind(this)); | ||
clearInterval(this.interval); | ||
this.alphabetContainer.removeEventListener('touchstart', ev => this.focusEvent(ev, 'touchstart')); | ||
this.alphabetContainer.removeEventListener('touchmove', ev => this.focusEvent(ev, 'touchmove')); | ||
this.alphabetContainer.removeEventListener('touchend', () => this.focusEnd()); | ||
this.alphabetContainer.removeEventListener('mouseenter', ev => this.focusEvent(ev, 'mouseenter')); | ||
this.alphabetContainer.removeEventListener('mousemove', ev => this.focusEvent(ev, 'mousemove')); | ||
this.alphabetContainer.removeEventListener('mouseleave', () => this.focusEnd()); | ||
this.alphabetContainer.removeEventListener('click', ev => this.focusEvent(ev, 'click')); | ||
window.removeEventListener('resize', this.checkVisibleLetters.bind(this)); | ||
clearInterval(this._offsetSizeCheckIntervalTimer); | ||
} | ||
onResize() { | ||
get alphabetContainer() { | ||
return this.el.shadowRoot.querySelector('.container'); | ||
} | ||
checkVisibleLetters(force) { | ||
let height = this.alphabetContainer.clientHeight; | ||
if (!force && height === this._lastHeight) { | ||
return; | ||
} | ||
this._lastHeight = height; | ||
let newAlphabet = this.alphabet; | ||
const height = this.el.shadowRoot.querySelector('.container').clientHeight; | ||
// const newLetterIndex = Math.ceil((this.letterIndex * height) / letterSize); | ||
// this.letterIndex = newLetterIndex; | ||
// this.onLetterIndexChange(newLetterIndex); | ||
const numLetters = this.alphabet.length; | ||
const basicLettersSize = this.fontSize * (numLetters - 5 > 0 ? numLetters - 5 : 0); | ||
let specialLettersSize = this.magnificationSize; | ||
for (let i = 0; i < 5; i++) { | ||
specialLettersSize += this.getMultiplier(i) * this.magnificationSize * 2; | ||
let letterSpacing = 0; | ||
let letterSize = this.stringToNumber(getComputedStyle(this.alphabetContainer).getPropertyValue('font-size')); | ||
if (this.letterMagnification) { | ||
letterSize = letterSize * this.magnificationMultiplier * 0.6; | ||
} | ||
const averageLetterSize = (basicLettersSize + specialLettersSize) / numLetters; | ||
if (height / averageLetterSize < numLetters) { | ||
let numHiddenLetters = numLetters - Math.floor(height / averageLetterSize); | ||
//Calculate actual letter spacing | ||
if (typeof this.letterSpacing === 'number') { | ||
letterSpacing = this.letterSpacing; | ||
} | ||
else if (typeof this.letterSpacing === 'string') { | ||
letterSpacing = this.stringToNumber(this.letterSpacing); | ||
if (this.letterSpacing.endsWith('%')) { | ||
letterSpacing = height * (letterSpacing / 100); | ||
} | ||
} | ||
letterSize = letterSize + letterSpacing; | ||
//Remove invalid letters (if set and necessary) | ||
if (this.prioritizeHidingInvalidLetters && !!this.validLetters && height / letterSize < newAlphabet.length) { | ||
newAlphabet = this.validLetters; | ||
} | ||
//Check if there is enough free space for letters | ||
this._lettersShortened = height / letterSize < newAlphabet.length; | ||
if (this._lettersShortened) { | ||
const numHiddenLetters = newAlphabet.length - Math.floor(height / letterSize); | ||
if (numHiddenLetters === newAlphabet.length) | ||
newAlphabet = []; | ||
//determine how many letters to hide | ||
const hiddenHalves = this.getNumHiddenHalves(numHiddenLetters, numLetters) + 1; | ||
const hiddenHalves = this.getNumHiddenHalves(numHiddenLetters, newAlphabet.length) + 1; | ||
// (this.magnifyDividers || numHiddenLetters > newAlphabet.length - 2 ? 1 : 0); | ||
//split alphabet into two halves | ||
let alphabet1 = this.alphabet.slice(0, Math.ceil(numLetters / 2)); | ||
let alphabet2 = this.alphabet.slice(Math.floor(numLetters / 2)).reverse(); | ||
let alphabet1 = newAlphabet.slice(0, Math.ceil(newAlphabet.length / 2)); | ||
let alphabet2 = newAlphabet.slice(Math.floor(newAlphabet.length / 2)).reverse(); | ||
for (let i = 0; i < hiddenHalves; i++) { | ||
alphabet1 = alphabet1.filter((_, index) => { | ||
return index % 2 === 0; | ||
}); | ||
alphabet2 = alphabet2.filter((_, index) => { | ||
return index % 2 === 0; | ||
}); | ||
alphabet1 = alphabet1.filter((_, i) => i % 2 === 0); | ||
alphabet2 = alphabet2.filter((_, i) => i % 2 === 0); | ||
} | ||
@@ -93,3 +161,4 @@ //insert dots between letters | ||
if (i > 0) { | ||
prev.push('·'); | ||
if (this.overflowDivider) | ||
prev.push(this.overflowDivider); | ||
} | ||
@@ -99,6 +168,6 @@ prev.push(curr); | ||
}, []); | ||
//insert dots between letters | ||
alphabet2 = alphabet2.reduce((prev, curr, i) => { | ||
if (i > 0) { | ||
prev.push('·'); | ||
if (this.overflowDivider) | ||
prev.push(this.overflowDivider); | ||
} | ||
@@ -108,4 +177,4 @@ prev.push(curr); | ||
}, []); | ||
if (this.alphabet.length % 2 === 0) | ||
alphabet1.push('·'); | ||
if (this.alphabet.length % 2 === 0 && this.overflowDivider) | ||
alphabet1.push(this.overflowDivider); | ||
newAlphabet = alphabet1.concat(alphabet2.reverse()); | ||
@@ -121,129 +190,111 @@ } | ||
} | ||
isActive(i) { | ||
return this.letterIndex === i + 1 && this.active; | ||
isValid(letter) { | ||
var _a; | ||
return ((_a = this.validLetters) === null || _a === void 0 ? void 0 : _a.includes(letter)) !== false || letter === this.overflowDivider; | ||
} | ||
getMultiplier(i) { | ||
if (!this.active || !this.letterMagnification || (this.visibleLetters.length > i && this.visibleLetters[i] === '·')) | ||
return 1; | ||
const multiplier = this.magnificationSize / this.fontSize; | ||
if (this.letterIndex === i + 1 && this.active) | ||
return multiplier; | ||
else if (i + 1 === this.letterIndex - 1 || i + 1 === this.letterIndex + 1) | ||
return multiplier * 0.8; | ||
else if (i + 1 === this.letterIndex - 2 || i + 1 === this.letterIndex + 2) | ||
return multiplier * 0.7; | ||
else if (i + 1 === this.letterIndex - 3 || i + 1 === this.letterIndex + 3) | ||
return multiplier * 0.6; | ||
else | ||
return 1; | ||
} | ||
//*** Touch Events ***// | ||
touchStart(event) { | ||
this.scrolling.emit(true); | ||
this.active = true; | ||
this.touchMove(event); | ||
} | ||
//updates on every form of touch | ||
touchMove(event) { | ||
const x = event.touches[0].clientX; | ||
const y = event.touches[0].clientY; | ||
const newLetterIndex = this.getLetterIndexFromCoordinates(x, y); | ||
if (newLetterIndex) | ||
this.onLetterIndexChange(newLetterIndex); | ||
} | ||
touchEnd() { | ||
this.lastLetterIndex = null; | ||
this.scrolling.emit(false); | ||
this.active = false; | ||
} | ||
//*** End Touch Events ***// | ||
//*** Mouse Events ***// | ||
mouseEnter(event) { | ||
if (this.navigateOnHover) { | ||
this.scrolling.emit(true); | ||
focusEvent(event, type) { | ||
var _a, _b, _c, _d; | ||
if (!this._lastEmittedActive) { | ||
this.isActive.emit((this._lastEmittedActive = true)); | ||
} | ||
this.active = true; | ||
this.mouseMove(event); | ||
} | ||
mouseMove(event) { | ||
const x = event.clientX; | ||
const y = event.clientY; | ||
const newLetterIndex = this.getLetterIndexFromCoordinates(x, y); | ||
if (!this.navigateOnHover) | ||
return; | ||
if (newLetterIndex) | ||
this.onLetterIndexChange(newLetterIndex); | ||
} | ||
mouseLeave() { | ||
if (this.navigateOnHover) { | ||
this.scrolling.emit(false); | ||
if (type === 'click') | ||
this._isComponentActive = false; | ||
else if (!this._isComponentActive) | ||
this._isComponentActive = true; | ||
this.setLetterFromCoordinates((_b = (_a = event.touches) === null || _a === void 0 ? void 0 : _a[0].clientX) !== null && _b !== void 0 ? _b : event.clientX, (_d = (_c = event.touches) === null || _c === void 0 ? void 0 : _c[0].clientY) !== null && _d !== void 0 ? _d : event.clientY); | ||
if (this._lastEmittedLetter !== this.letterSelected && (this.navigateOnHover || !type.includes('mouse'))) { | ||
this.letterChange.emit((this._lastEmittedLetter = this.letterSelected)); | ||
} | ||
this.lastLetterIndex = null; | ||
this.active = false; | ||
} | ||
click(event) { | ||
event.preventDefault(); | ||
this.lastLetterIndex = null; | ||
const x = event.clientX; | ||
const y = event.clientY; | ||
const newLetterIndex = this.getLetterIndexFromCoordinates(x, y); | ||
this.onLetterIndexChange(newLetterIndex); | ||
focusEnd() { | ||
this.isActive.emit((this._isComponentActive = this._lastEmittedActive = false)); | ||
} | ||
//*** End Mouse Events ***// | ||
//*** Logic ***// | ||
getLetterIndexFromCoordinates(x, y) { | ||
let letterIndex = null; | ||
const aTop = this.el.shadowRoot.querySelector('.container').getBoundingClientRect().top; | ||
const aHeight = this.el.shadowRoot.querySelector('.container').clientHeight; | ||
const index = Math.ceil(((y - aTop) / aHeight) * this.alphabet.length); | ||
const aRightX = this.el.shadowRoot.querySelector('.container').getBoundingClientRect().right; | ||
const aLeftX = this.el.shadowRoot.querySelector('.container').getBoundingClientRect().left; | ||
if (index <= this.alphabet.length && index > 0) { | ||
if (this.exactX) { | ||
if (x < aRightX && x > aLeftX) { | ||
letterIndex = Math.ceil(index); | ||
} | ||
setLetterFromCoordinates(x, y) { | ||
if (this.exactX) { | ||
const rightX = this.alphabetContainer.getBoundingClientRect().right; | ||
const leftX = this.alphabetContainer.getBoundingClientRect().left; | ||
this._isInBounds = x > leftX && x < rightX; | ||
if (!this._isInBounds) { | ||
this.visualLetterIndex = this.visualLetterIndex = null; | ||
return; | ||
} | ||
} | ||
const height = this.alphabetContainer.clientHeight; | ||
//Letters drew outside the viewport or host padding may cause values outsize height boundries (Usage of min/max) | ||
const top = Math.min(Math.max(0, y - this.alphabetContainer.getBoundingClientRect().top), height); | ||
let topRelative = (top / height) * (this.visibleLetters.length - 1); | ||
const preferNext = Math.round(topRelative) < topRelative; | ||
topRelative = Math.round(topRelative); | ||
this.magIndex = topRelative; | ||
//Set visualLetterIndex to the closest valid letter | ||
this.visualLetterIndex = this.getClosestValidLetterIndex(this.visibleLetters, topRelative, preferNext); | ||
if (this._lettersShortened) { | ||
if (this.validLetters) { | ||
this.letterSelected = this.validLetters[Math.round((top / height) * (this.validLetters.length - 1))]; | ||
} | ||
else { | ||
letterIndex = Math.ceil(index); | ||
this.letterSelected = this.alphabet[this.getClosestValidLetterIndex(this.alphabet, topRelative, preferNext)]; | ||
} | ||
} | ||
if (letterIndex) | ||
this.letterIndex = Math.round(letterIndex * (this.visibleLetters.length / this.alphabet.length)); | ||
return letterIndex; | ||
else { | ||
this.letterSelected = this.visibleLetters[this.visualLetterIndex]; | ||
} | ||
} | ||
//emits the letter change | ||
onLetterIndexChange(value) { | ||
if (!value) | ||
return; | ||
if (this.lastLetterIndex === value) | ||
return; | ||
this.lastLetterIndex = value; | ||
const letter = this.validLetters ? this.getClosestValidLetter(value) : this.alphabet[value - 1]; | ||
this.letterChange.emit(letter); | ||
getClosestValidLetterIndex(alphabet, visualLetterIndex, preferNext) { | ||
const lowercaseAlphabet = alphabet.map(l => l.toLowerCase()); | ||
const lowercaseValidLetters = this.validLetters.map(l => l.toLowerCase()); | ||
const validLettersAsNumbers = lowercaseValidLetters.map(l => lowercaseAlphabet.indexOf(l)); | ||
return validLettersAsNumbers.length > 0 | ||
? validLettersAsNumbers.reduce((prev, curr) => preferNext | ||
? Math.abs(curr - visualLetterIndex) > Math.abs(prev - visualLetterIndex) | ||
? prev | ||
: curr | ||
: Math.abs(curr - visualLetterIndex) < Math.abs(prev - visualLetterIndex) | ||
? curr | ||
: prev) | ||
: null; | ||
} | ||
//gets closest letter | ||
getClosestValidLetter(letterIndex) { | ||
const lowercaseAlphabet = this.alphabet.map(letter => letter.toLowerCase()); | ||
const lowercaseValidLetters = this.validLetters.map(letter => letter.toLowerCase()); | ||
const validLettersAsNumbers = lowercaseValidLetters.map(letter => lowercaseAlphabet.indexOf(letter) + 1); | ||
const closest = validLettersAsNumbers.reduce((prev, curr) => (Math.abs(curr - letterIndex) < Math.abs(prev - letterIndex) ? curr : prev)); | ||
return this.alphabet[closest - 1]; | ||
stringToNumber(value) { | ||
return Number(value === null || value === void 0 ? void 0 : value.match(/[\.\d]+/)[0]); | ||
} | ||
getLetterStyle(index) { | ||
if ((this.magIndex === undefined && this.magIndex === null) || | ||
(!this.magnifyDividers && this.visibleLetters[index] === this.overflowDivider) || | ||
(this.disableInvalidLetters && !this.isValid(this.visibleLetters[index]))) | ||
return {}; | ||
const lettersOnly = this.visibleLetters.filter(l => l !== this.overflowDivider); | ||
const mappedIndex = Math.round((index / this.visibleLetters.length) * lettersOnly.length); | ||
const mappedMagIndex = Math.round((this.magIndex / this.visibleLetters.length) * lettersOnly.length); | ||
let relativeIndex = this.magnifyDividers ? Math.abs(this.magIndex - index) : Math.abs(mappedMagIndex - mappedIndex); | ||
const magnification = relativeIndex < this.magnificationCurve.length - 1 ? this.magnificationCurve[relativeIndex] * (this.magnificationMultiplier - 1) + 1 : 1; | ||
const style = { | ||
transform: `scale(${magnification})`, | ||
zIndex: this.magIndex === index ? 1 : 0, | ||
}; | ||
return this._isInBounds && this._isComponentActive && this.letterMagnification ? style : { transform: 'scale(1)', zIndex: 0 }; | ||
} | ||
render() { | ||
return (h(Host, { style: { opacity: !this.rendering ? '1' : '0' } }, h("div", { class: { | ||
var _a; | ||
return (h(Host, null, h("div", { class: { | ||
container: true, | ||
}, style: { | ||
width: this.fontSize + 'px', | ||
} }, this.visibleLetters.map((letter, i) => { | ||
} }, (_a = this.visibleLetters) === null || _a === void 0 ? void 0 : _a.map((letter, i) => { | ||
return (h("div", { key: `${letter}-${i}`, class: { | ||
letter: true, | ||
active: this.isActive(i) && this.letterMagnification, | ||
}, id: this.visibleLetters[i] }, h("label", { style: { | ||
transform: `translate(-50%, -50%) scale(${this.getMultiplier(i)})`, | ||
fontSize: this.fontSize + 'px', | ||
} }, letter))); | ||
'letter': true, | ||
'letter-disabled': this.disableInvalidLetters && !this.isValid(letter), | ||
}, style: this.getLetterStyle(i), id: this.visibleLetters[i] }, h("label", null, " ", letter))); | ||
})))); | ||
} | ||
get el() { return getElement(this); } | ||
static get watchers() { return { | ||
"alphabet": ["onAlphabetChange"], | ||
"overflowDivider": ["onOverflowDividerChange"], | ||
"validLetters": ["onValidLettersChange"], | ||
"disableInvalidLetters": ["onDisableInvalidLettersChange"], | ||
"prioritizeHidingInvalidLetters": ["onPrioritizeHidingInvalidLettersChange"], | ||
"magnificationMultiplier": ["onMagnificationMultiplierChange"], | ||
"magnificationCurve": ["onMagnificationCurveChange"], | ||
"exactX": ["onExactXChange"], | ||
"letterSpacing": ["onLetterSpacingChange"], | ||
"offsetSizeCheckInterval": ["onOffsetSizeCheckIntervalChange"] | ||
}; } | ||
}; | ||
@@ -250,0 +301,0 @@ IndexScrollbar.style = indexScrollbarCss; |
@@ -1,2 +0,2 @@ | ||
import { p as promiseResolve, b as bootstrapLazy } from './index-b388e836.js'; | ||
import { p as promiseResolve, b as bootstrapLazy } from './index-5f889f85.js'; | ||
@@ -16,3 +16,3 @@ /* | ||
patchBrowser().then(options => { | ||
return bootstrapLazy([["index-scrollbar",[[1,"index-scrollbar",{"letterMagnification":[4,"letter-magnification"],"exactX":[4,"exact-x"],"fontSize":[2,"font-size"],"magnificationSize":[2,"magnification-size"],"alphabet":[16],"validLetters":[16],"navigateOnHover":[4,"navigate-on-hover"],"visibleLetters":[32],"letterIndex":[32],"active":[32],"rendering":[32]}]]]], options); | ||
return bootstrapLazy([["index-scrollbar",[[1,"index-scrollbar",{"alphabet":[16],"overflowDivider":[1,"overflow-divider"],"validLetters":[16],"disableInvalidLetters":[4,"disable-invalid-letters"],"prioritizeHidingInvalidLetters":[4,"prioritize-hiding-invalid-letters"],"letterMagnification":[4,"letter-magnification"],"magnifyDividers":[4,"magnify-dividers"],"magnificationMultiplier":[2,"magnification-multiplier"],"magnificationCurve":[16],"exactX":[4,"exact-x"],"navigateOnHover":[4,"navigate-on-hover"],"letterSpacing":[8,"letter-spacing"],"offsetSizeCheckInterval":[2,"offset-size-check-interval"],"_isComponentActive":[32],"visibleLetters":[32],"_lastEmittedLetter":[32],"magIndex":[32],"_isInBounds":[32],"visualLetterIndex":[32],"letterSelected":[32]}]]]], options); | ||
}); |
@@ -1,2 +0,2 @@ | ||
import { p as promiseResolve, b as bootstrapLazy } from './index-b388e836.js'; | ||
import { p as promiseResolve, b as bootstrapLazy } from './index-5f889f85.js'; | ||
@@ -13,3 +13,3 @@ /* | ||
return patchEsm().then(() => { | ||
return bootstrapLazy([["index-scrollbar",[[1,"index-scrollbar",{"letterMagnification":[4,"letter-magnification"],"exactX":[4,"exact-x"],"fontSize":[2,"font-size"],"magnificationSize":[2,"magnification-size"],"alphabet":[16],"validLetters":[16],"navigateOnHover":[4,"navigate-on-hover"],"visibleLetters":[32],"letterIndex":[32],"active":[32],"rendering":[32]}]]]], options); | ||
return bootstrapLazy([["index-scrollbar",[[1,"index-scrollbar",{"alphabet":[16],"overflowDivider":[1,"overflow-divider"],"validLetters":[16],"disableInvalidLetters":[4,"disable-invalid-letters"],"prioritizeHidingInvalidLetters":[4,"prioritize-hiding-invalid-letters"],"letterMagnification":[4,"letter-magnification"],"magnifyDividers":[4,"magnify-dividers"],"magnificationMultiplier":[2,"magnification-multiplier"],"magnificationCurve":[16],"exactX":[4,"exact-x"],"navigateOnHover":[4,"navigate-on-hover"],"letterSpacing":[8,"letter-spacing"],"offsetSizeCheckInterval":[2,"offset-size-check-interval"],"_isComponentActive":[32],"visibleLetters":[32],"_lastEmittedLetter":[32],"magIndex":[32],"_isInBounds":[32],"visualLetterIndex":[32],"letterSelected":[32]}]]]], options); | ||
}); | ||
@@ -16,0 +16,0 @@ }; |
@@ -1,1 +0,1 @@ | ||
import{p as e,b as i}from"./p-57af5440.js";(()=>{const i=import.meta.url,t={};return""!==i&&(t.resourcesUrl=new URL(".",i).href),e(t)})().then((e=>i([["p-00d53cba",[[1,"index-scrollbar",{letterMagnification:[4,"letter-magnification"],exactX:[4,"exact-x"],fontSize:[2,"font-size"],magnificationSize:[2,"magnification-size"],alphabet:[16],validLetters:[16],navigateOnHover:[4,"navigate-on-hover"],visibleLetters:[32],letterIndex:[32],active:[32],rendering:[32]}]]]],e))); | ||
import{p as e,b as i}from"./p-d4d31fe4.js";(()=>{const i=import.meta.url,t={};return""!==i&&(t.resourcesUrl=new URL(".",i).href),e(t)})().then((e=>i([["p-3e01e4ac",[[1,"index-scrollbar",{alphabet:[16],overflowDivider:[1,"overflow-divider"],validLetters:[16],disableInvalidLetters:[4,"disable-invalid-letters"],prioritizeHidingInvalidLetters:[4,"prioritize-hiding-invalid-letters"],letterMagnification:[4,"letter-magnification"],magnifyDividers:[4,"magnify-dividers"],magnificationMultiplier:[2,"magnification-multiplier"],magnificationCurve:[16],exactX:[4,"exact-x"],navigateOnHover:[4,"navigate-on-hover"],letterSpacing:[8,"letter-spacing"],offsetSizeCheckInterval:[2,"offset-size-check-interval"],_isComponentActive:[32],visibleLetters:[32],_lastEmittedLetter:[32],magIndex:[32],_isInBounds:[32],visualLetterIndex:[32],letterSelected:[32]}]]]],e))); |
@@ -11,8 +11,14 @@ /* eslint-disable */ | ||
"alphabet": Array<string>; | ||
"disableInvalidLetters": boolean; | ||
"exactX": boolean; | ||
"fontSize": number; | ||
"letterMagnification": boolean; | ||
"magnificationSize": number; | ||
"letterSpacing": number | string | null; | ||
"magnificationCurve": Array<number>; | ||
"magnificationMultiplier": number; | ||
"magnifyDividers": boolean; | ||
"navigateOnHover": boolean; | ||
"validLetters": Array<string>; | ||
"offsetSizeCheckInterval": number; | ||
"overflowDivider": string | undefined | null; | ||
"prioritizeHidingInvalidLetters": boolean; | ||
"validLetters": string[]; | ||
} | ||
@@ -34,10 +40,16 @@ } | ||
"alphabet"?: Array<string>; | ||
"disableInvalidLetters"?: boolean; | ||
"exactX"?: boolean; | ||
"fontSize"?: number; | ||
"letterMagnification"?: boolean; | ||
"magnificationSize"?: number; | ||
"letterSpacing"?: number | string | null; | ||
"magnificationCurve"?: Array<number>; | ||
"magnificationMultiplier"?: number; | ||
"magnifyDividers"?: boolean; | ||
"navigateOnHover"?: boolean; | ||
"offsetSizeCheckInterval"?: number; | ||
"onIsActive"?: (event: CustomEvent<boolean>) => void; | ||
"onLetterChange"?: (event: CustomEvent<string>) => void; | ||
"onScrolling"?: (event: CustomEvent<boolean>) => void; | ||
"validLetters"?: Array<string>; | ||
"overflowDivider"?: string | undefined | null; | ||
"prioritizeHidingInvalidLetters"?: boolean; | ||
"validLetters"?: string[]; | ||
} | ||
@@ -44,0 +56,0 @@ interface IntrinsicElements { |
import { EventEmitter } from '../../stencil-public-runtime'; | ||
export declare class IndexScrollbar { | ||
el: HTMLElement; | ||
alphabet: Array<string>; | ||
onAlphabetChange(value: any): void; | ||
overflowDivider: string | undefined | null; | ||
onOverflowDividerChange(value: any): void; | ||
validLetters: string[]; | ||
onValidLettersChange(): void; | ||
disableInvalidLetters: boolean; | ||
onDisableInvalidLettersChange(): void; | ||
prioritizeHidingInvalidLetters: boolean; | ||
onPrioritizeHidingInvalidLettersChange(): void; | ||
letterMagnification: boolean; | ||
magnifyDividers: boolean; | ||
magnificationMultiplier: number; | ||
onMagnificationMultiplierChange(): void; | ||
magnificationCurve: Array<number>; | ||
onMagnificationCurveChange(value: any): void; | ||
exactX: boolean; | ||
fontSize: number; | ||
magnificationSize: number; | ||
alphabet: Array<string>; | ||
validLetters: Array<string>; | ||
onExactXChange(): void; | ||
navigateOnHover: boolean; | ||
letterSpacing: number | string | null; | ||
onLetterSpacingChange(value: any): void; | ||
offsetSizeCheckInterval: number; | ||
onOffsetSizeCheckIntervalChange(value: number): void; | ||
private _offsetSizeCheckIntervalTimer; | ||
letterChange: EventEmitter<string>; | ||
isActive: EventEmitter<boolean>; | ||
private _lastEmittedActive; | ||
_isComponentActive: boolean; | ||
scrolling: EventEmitter<boolean>; | ||
visibleLetters: Array<string>; | ||
letterIndex: number; | ||
active: boolean; | ||
rendering: boolean; | ||
private lastLetterIndex; | ||
interval: any; | ||
height: number; | ||
constructor(); | ||
@@ -24,17 +38,20 @@ connectedCallback(): void; | ||
disconnectedCallback(): void; | ||
onResize(): void; | ||
get alphabetContainer(): Element; | ||
checkVisibleLetters(force?: boolean): void; | ||
private _lastHeight; | ||
private _lettersShortened; | ||
getNumHiddenHalves(numHiddenLetters: number, total: number): any; | ||
isActive(i: number): boolean; | ||
getMultiplier(i: number): number; | ||
touchStart(event: any): void; | ||
touchMove(event: any): void; | ||
touchEnd(): void; | ||
mouseEnter(event: any): void; | ||
mouseMove(event: any): void; | ||
mouseLeave(): void; | ||
click(event: any): void; | ||
private getLetterIndexFromCoordinates; | ||
private onLetterIndexChange; | ||
private getClosestValidLetter; | ||
isValid(letter: string): boolean; | ||
focusEvent(event: MouseEvent | TouchEvent | any, type?: string): void; | ||
_lastEmittedLetter: string; | ||
focusEnd(): void; | ||
magIndex: number; | ||
_isInBounds: boolean; | ||
private setLetterFromCoordinates; | ||
visualLetterIndex: number; | ||
letterSelected: string; | ||
private getClosestValidLetterIndex; | ||
private stringToNumber; | ||
getLetterStyle(index: number): any; | ||
render(): any; | ||
} |
{ | ||
"name": "index-scrollbar", | ||
"version": "0.0.12", | ||
"version": "1.0.0", | ||
"description": "Stencil Component Starter", | ||
@@ -17,2 +17,6 @@ "main": "dist/index.cjs.js", | ||
], | ||
"repository": { | ||
"type": "git", | ||
"url": "https://github.com/mooalot/index-scrollbar" | ||
}, | ||
"scripts": { | ||
@@ -23,3 +27,4 @@ "build": "stencil build --prod", | ||
"test.watch": "stencil test --spec --e2e --watchAll", | ||
"generate": "stencil generate" | ||
"generate": "stencil generate", | ||
"publish": "npm run build && npm publish --access=public" | ||
}, | ||
@@ -26,0 +31,0 @@ "dependencies": { |
@@ -5,9 +5,7 @@ # Index Scroll Bar | ||
| Inactive Scroll Bar | Active Scroll Bar | | ||
| :-------------------------------------------------------------------------------------------------------------------------: | :------------------------------------------------------------------------------------------------------------------------: | | ||
| <img src="https://github.com/mooalot/alphabetical-scroll-bar/blob/main/projects/example/src/assets/image2.PNG" width="300"> | <img src="https://github.com/mooalot/alphabetical-scroll-bar/blob/main/projects/example/src/assets/image.PNG" width="300"> | | ||
A very responsive, simple and customizable alphabetical scroll bar (index scrollbar) that can be used in any project as a web component. | ||
## Version 2.0.0 and up | ||
![Alt Text](https://github.com/mooalot/alphabetical-scroll-bar/blob/main/projects/example/src/assets/alphabetical-scroll-bar.gif) | ||
New features: | ||
## New features | ||
@@ -39,27 +37,39 @@ - You can now use your own custom alphabet. | ||
``` | ||
<index-scrollbar><index-scrollbar> | ||
<index-scrollbar | ||
[alphabet]="'ABCDEFGHIJKLMNOPQRSTUVWXYZ'.split('')" | ||
[overflowDivider]="'-'" | ||
[validLetters]="letterGroups" | ||
[disableInvalidLetters]="true" | ||
[prioritizeHidingInvalidLetters]="true" | ||
[letterMagnification]="true" | ||
[magnifyDividers]="true" | ||
[magnificationMultiplier]="2" | ||
[magnificationCurve]="[1, .7, .6, .4, .2, 0]" | ||
[exactX]="false" | ||
[navigateOnHover]="true" | ||
letterSpacing="'1%'" | ||
[offsetSizeCheckInterval]="100" | ||
(letterChange)="goToLetterGroup($event)" | ||
(isActive)="!$event && enableScroll()" | ||
></index-scrollbar> | ||
``` | ||
**_Inputs_** | ||
| Input/Output | Parameter | Description | default | type | | ||
| ------------ | ------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------- | --------------------------- | | ||
| Input | alphabet | Custom version of the alphabet. | `ABCDEFGHIJKLMNOPQRSTUVWXYZ` | Array or string | | ||
| Input | overflowDivider | Custom divider to be used when the screen size is smaller than the scroll bar. Can be set to `undefined` or `null`. | `·` | string or undefined or null | | ||
| Input | validLetters | Array of the possible letters that are available in the scrollable content. For example, if you only have 5 different letter dividers `A`, `D`, `F`, `I`, and `R`, you would want to pass these into `validLetters`. If you did not, when you tap on `Z` in the alphabetical scroll bar, nothing will happen. If you do include `validLetters`, your view would be taken to the next closest letter, which in this case is `R`. This is not a requirement, but it will make your alphabetical scroll bar much more robust. | `ABCDEFGHIJKLMNOPQRSTUVWXYZ` | Array | | ||
| Input | disableInvalidLetters | Boolean that determines whether or not to disable the invalid letters. Disabled letters are greyed out and will not magnify. If you do not, the invalid letters will be shown as disabled. | `false` | boolean | | ||
| Input | prioritizeHidingInvalidLetters | If true, the scroll bar will prioritize hiding invalid letters before subsituting `overflowDividers`. This is useful if you have a lot of invalid letters and you want to show the scroll bar without the invalid letters. | `false` | boolean | | ||
| Input | letterMagnification | This will create a magnification effect on the alphabetical scroll bar when the user touches it or hovers over it. | `true` | boolean | | ||
| Input | magnifyDividers | This input will magnify the dividers when the user touches them or hovers over them. | `false` | boolean | | ||
| Input | magnificationMultiplier | How much to multiply the size of magnified letters. | `2` | number | | ||
| Input | magnificationCurve | This is the curve that the magnification will follow as the letters magnify. This input must range between 1 and 0. Closer to one means larger. Closer to zero means smaller. The first index determines the size of the selected letter, the following indexes determine size for neighboring letters. Play around with it to see what looks best for your application. | `[1, 0.7, 0.5, 0.3, 0.1]` | Array | | ||
| Input | exactX | When `false`, this means the user does not have to be accurate along the x direction of the screen (after they have touched the scroll bar), meaning they can slide their finger freely along the x axis while still changing the scroll value. If set to `true`, the user will have to remain inside the scroll bar to continue navigating (I think false gives it a smoother feel). | `false` | boolean | | ||
| Input | navigateOnHover | This means that the user will have to tap on the scroll bar to navigate to a new letter. If set to `true`, the user will be able to navigate to a new letter by hovering over the scroll bar. | `false` | boolean | | ||
| Input | letterSpacing | This is the spacing between letters. It defaults to `1%`. Accepts a string with a percentage value (`1%`) or a number as a pixel value. Percentage is the percent of the scroll bar height. | `1%` | string or number | | ||
| Input | offsetSizeCheckInterval | This is the interval in milliseconds that the scroll bar will check to see if the size of the scroll bar has changed. Useful, if e.g. a splitter-component resizes the scroll-bar but not the window itself. | `0` (no interval) | number | | ||
| Output | letterChange | Every time the user scrolls through the alphabetical scroll bar to a new letter, this emitter will output the letter (as a `string`) that the user scrolled to. This will allow you to scroll to the appropriate letter divider. The example project above shows one method of how this function can be used. You can add things like haptics in the function this calls. | none | none | | ||
| Output | isActive | EventEmitter that will emit when the user releases their finger from the scroll bar. This is used to stop any unwanted scroll glitches while the user is using the alphabetical scroll bar. See example for more information. (MOBILE ONLY) | none | none | | ||
**letterMagnification** defaults to `true`. This feature will create a magnification effect on the index scroll bar when the user touches it or hovers over it. | ||
**exactX** defaults to `false`. When `false`, this means the user does not have to be accurate along the x direction of the screen (after they have touched the scroll bar), meaning they can slide their finger freely along the x axis while still changing the scroll value. If set to `true`, the user will have to remain inside the scroll bar to continue navigating (I think false gives it a smoother feel). (MOBILE ONLY) | ||
**alphabet** allows you to enter your own custom version of the alphabet. It defaults to an all caps alphabet. | ||
**validLetters** is an array of the possible letters that are available in the scrollable content. For example, if you only have 5 different letter dividers `A`, `D`, `F`, `I`, and `R`, you would want to pass these into `validLetters`. If you did not, when you tap on `Z` in the index scroll bar, nothing will happen. If you do include `validLetters`, your view would be taken to the next closest letter, which in this case is `R`. This is not a requirement, but it will make your index scroll bar much more robust. | ||
**navigateOnHover** defaults to `false`. This means that the user will have to tap on the scroll bar to navigate to a new letter. If set to `true`, the user will be able to navigate to a new letter by hovering over the scroll bar. (DESKTOP ONLY) | ||
**fontSize** defaults to `16`. This is the size of the font used in the scroll bar. | ||
**magnificationSize** defaults to `36`. This is the size of the font used in the scroll bar when magnified. | ||
**_Output Events_** | ||
**letterChange** is an eventEmitter. Every time the user scrolls through the index scroll bar to a new letter, this emitter will output the letter (as a `string`) that the user scrolled to. This will allow you to scroll to the appropriate letter divider. The example project above shows one method of how this function can be used. You can add things like haptics in the function this calls. | ||
**endTouch** is an eventEmitter that will emit when the user releases their finger from the scroll bar. This is used to stop any unwanted scroll glitches while the user is using the index scroll bar. See example for more information. (MOBILE ONLY) | ||
\*Also note that the `index-scrollbar` element must have a high z-index to be above dividers and other elements. | ||
\*Also note that the `alphabetical-scroll` element must have a high z-index to be above dividers and other elements. |
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
No repository
Supply chain riskPackage does not have a linked source code repository. Without this field, a package will have no reference to the location of the source code use to generate the package.
Found 1 instance in 1 package
No v1
QualityPackage is not semver >=1. This means it is not stable and does not support ^ ranges.
Found 1 instance in 1 package
399739
6360
1
74
1
3