svelte-range-slider-pips
Version:
Multi-Thumb, Accessible, Beautiful Range Slider with Pips
1,755 lines (1,626 loc) • 123 kB
JavaScript
/**
* svelte-range-slider-pips ~ 3.2.2
* Multi-Thumb, Accessible, Beautiful Range Slider with Pips
* Project home: https://simeydotme.github.io/svelte-range-slider-pips/
* © 2025 Simon Goellner <simey.me@gmail.com> ~ MPL-2.0 License
* Published: 23/2/2025
*/
/** @returns {void} */
function noop() {}
function run(fn) {
return fn();
}
function blank_object() {
return Object.create(null);
}
/**
* @param {Function[]} fns
* @returns {void}
*/
function run_all(fns) {
fns.forEach(run);
}
/**
* @param {any} thing
* @returns {thing is Function}
*/
function is_function(thing) {
return typeof thing === 'function';
}
/** @returns {boolean} */
function safe_not_equal(a, b) {
return a != a ? b == b : a !== b || (a && typeof a === 'object') || typeof a === 'function';
}
/** @returns {boolean} */
function is_empty(obj) {
return Object.keys(obj).length === 0;
}
function subscribe(store, ...callbacks) {
if (store == null) {
for (const callback of callbacks) {
callback(undefined);
}
return noop;
}
const unsub = store.subscribe(...callbacks);
return unsub.unsubscribe ? () => unsub.unsubscribe() : unsub;
}
const is_client = typeof window !== 'undefined';
/** @type {() => number} */
let now = is_client ? () => window.performance.now() : () => Date.now();
let raf = is_client ? (cb) => requestAnimationFrame(cb) : noop;
const tasks = new Set();
/**
* @param {number} now
* @returns {void}
*/
function run_tasks(now) {
tasks.forEach((task) => {
if (!task.c(now)) {
tasks.delete(task);
task.f();
}
});
if (tasks.size !== 0) raf(run_tasks);
}
/**
* Creates a new task that runs on each raf frame
* until it returns a falsy value or is aborted
* @param {import('./private.js').TaskCallback} callback
* @returns {import('./private.js').Task}
*/
function loop(callback) {
/** @type {import('./private.js').TaskEntry} */
let task;
if (tasks.size === 0) raf(run_tasks);
return {
promise: new Promise((fulfill) => {
tasks.add((task = { c: callback, f: fulfill }));
}),
abort() {
tasks.delete(task);
}
};
}
/**
* @param {Node} target
* @param {Node} node
* @returns {void}
*/
function append(target, node) {
target.appendChild(node);
}
/**
* @param {Node} target
* @param {string} style_sheet_id
* @param {string} styles
* @returns {void}
*/
function append_styles(target, style_sheet_id, styles) {
const append_styles_to = get_root_for_style(target);
if (!append_styles_to.getElementById(style_sheet_id)) {
const style = element('style');
style.id = style_sheet_id;
style.textContent = styles;
append_stylesheet(append_styles_to, style);
}
}
/**
* @param {Node} node
* @returns {ShadowRoot | Document}
*/
function get_root_for_style(node) {
if (!node) return document;
const root = node.getRootNode ? node.getRootNode() : node.ownerDocument;
if (root && /** @type {ShadowRoot} */ (root).host) {
return /** @type {ShadowRoot} */ (root);
}
return node.ownerDocument;
}
/**
* @param {ShadowRoot | Document} node
* @param {HTMLStyleElement} style
* @returns {CSSStyleSheet}
*/
function append_stylesheet(node, style) {
append(/** @type {Document} */ (node).head || node, style);
return style.sheet;
}
/**
* @param {Node} target
* @param {Node} node
* @param {Node} [anchor]
* @returns {void}
*/
function insert(target, node, anchor) {
target.insertBefore(node, anchor || null);
}
/**
* @param {Node} node
* @returns {void}
*/
function detach(node) {
if (node.parentNode) {
node.parentNode.removeChild(node);
}
}
/**
* @returns {void} */
function destroy_each(iterations, detaching) {
for (let i = 0; i < iterations.length; i += 1) {
if (iterations[i]) iterations[i].d(detaching);
}
}
/**
* @template {keyof HTMLElementTagNameMap} K
* @param {K} name
* @returns {HTMLElementTagNameMap[K]}
*/
function element(name) {
return document.createElement(name);
}
/**
* @template {keyof SVGElementTagNameMap} K
* @param {K} name
* @returns {SVGElement}
*/
function svg_element(name) {
return document.createElementNS('http://www.w3.org/2000/svg', name);
}
/**
* @param {string} data
* @returns {Text}
*/
function text(data) {
return document.createTextNode(data);
}
/**
* @returns {Text} */
function space() {
return text(' ');
}
/**
* @returns {Text} */
function empty() {
return text('');
}
/**
* @param {EventTarget} node
* @param {string} event
* @param {EventListenerOrEventListenerObject} handler
* @param {boolean | AddEventListenerOptions | EventListenerOptions} [options]
* @returns {() => void}
*/
function listen(node, event, handler, options) {
node.addEventListener(event, handler, options);
return () => node.removeEventListener(event, handler, options);
}
/**
* @returns {(event: any) => any} */
function prevent_default(fn) {
return function (event) {
event.preventDefault();
// @ts-ignore
return fn.call(this, event);
};
}
/**
* @param {Element} node
* @param {string} attribute
* @param {string} [value]
* @returns {void}
*/
function attr(node, attribute, value) {
if (value == null) node.removeAttribute(attribute);
else if (node.getAttribute(attribute) !== value) node.setAttribute(attribute, value);
}
/**
* @param {Element} element
* @returns {ChildNode[]}
*/
function children(element) {
return Array.from(element.childNodes);
}
/**
* @param {Text} text
* @param {unknown} data
* @returns {void}
*/
function set_data(text, data) {
data = '' + data;
if (text.data === data) return;
text.data = /** @type {string} */ (data);
}
/**
* @returns {void} */
function toggle_class(element, name, toggle) {
// The `!!` is required because an `undefined` flag means flipping the current state.
element.classList.toggle(name, !!toggle);
}
/**
* @template T
* @param {string} type
* @param {T} [detail]
* @param {{ bubbles?: boolean, cancelable?: boolean }} [options]
* @returns {CustomEvent<T>}
*/
function custom_event(type, detail, { bubbles = false, cancelable = false } = {}) {
return new CustomEvent(type, { detail, bubbles, cancelable });
}
/** */
class HtmlTag {
/**
* @private
* @default false
*/
is_svg = false;
/** parent for creating node */
e = undefined;
/** html tag nodes */
n = undefined;
/** target */
t = undefined;
/** anchor */
a = undefined;
constructor(is_svg = false) {
this.is_svg = is_svg;
this.e = this.n = null;
}
/**
* @param {string} html
* @returns {void}
*/
c(html) {
this.h(html);
}
/**
* @param {string} html
* @param {HTMLElement | SVGElement} target
* @param {HTMLElement | SVGElement} anchor
* @returns {void}
*/
m(html, target, anchor = null) {
if (!this.e) {
if (this.is_svg)
this.e = svg_element(/** @type {keyof SVGElementTagNameMap} */ (target.nodeName));
/** #7364 target for <template> may be provided as #document-fragment(11) */ else
this.e = element(
/** @type {keyof HTMLElementTagNameMap} */ (
target.nodeType === 11 ? 'TEMPLATE' : target.nodeName
)
);
this.t =
target.tagName !== 'TEMPLATE'
? target
: /** @type {HTMLTemplateElement} */ (target).content;
this.c(html);
}
this.i(anchor);
}
/**
* @param {string} html
* @returns {void}
*/
h(html) {
this.e.innerHTML = html;
this.n = Array.from(
this.e.nodeName === 'TEMPLATE' ? this.e.content.childNodes : this.e.childNodes
);
}
/**
* @returns {void} */
i(anchor) {
for (let i = 0; i < this.n.length; i += 1) {
insert(this.t, this.n[i], anchor);
}
}
/**
* @param {string} html
* @returns {void}
*/
p(html) {
this.d();
this.h(html);
this.i(this.a);
}
/**
* @returns {void} */
d() {
this.n.forEach(detach);
}
}
/**
* @param {HTMLElement} element
* @returns {{}}
*/
function get_custom_elements_slots(element) {
const result = {};
element.childNodes.forEach(
/** @param {Element} node */ (node) => {
result[node.slot || 'default'] = true;
}
);
return result;
}
/**
* @typedef {Node & {
* claim_order?: number;
* hydrate_init?: true;
* actual_end_child?: NodeEx;
* childNodes: NodeListOf<NodeEx>;
* }} NodeEx
*/
/** @typedef {ChildNode & NodeEx} ChildNodeEx */
/** @typedef {NodeEx & { claim_order: number }} NodeEx2 */
/**
* @typedef {ChildNodeEx[] & {
* claim_info?: {
* last_index: number;
* total_claimed: number;
* };
* }} ChildNodeArray
*/
let current_component;
/** @returns {void} */
function set_current_component(component) {
current_component = component;
}
function get_current_component() {
if (!current_component) throw new Error('Function called outside component initialization');
return current_component;
}
/**
* Creates an event dispatcher that can be used to dispatch [component events](/docs#template-syntax-component-directives-on-eventname).
* Event dispatchers are functions that can take two arguments: `name` and `detail`.
*
* Component events created with `createEventDispatcher` create a
* [CustomEvent](https://developer.mozilla.org/en-US/docs/Web/API/CustomEvent).
* These events do not [bubble](https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Building_blocks/Events#Event_bubbling_and_capture).
* The `detail` argument corresponds to the [CustomEvent.detail](https://developer.mozilla.org/en-US/docs/Web/API/CustomEvent/detail)
* property and can contain any type of data.
*
* The event dispatcher can be typed to narrow the allowed event names and the type of the `detail` argument:
* ```ts
* const dispatch = createEventDispatcher<{
* loaded: never; // does not take a detail argument
* change: string; // takes a detail argument of type string, which is required
* optional: number | null; // takes an optional detail argument of type number
* }>();
* ```
*
* https://svelte.dev/docs/svelte#createeventdispatcher
* @template {Record<string, any>} [EventMap=any]
* @returns {import('./public.js').EventDispatcher<EventMap>}
*/
function createEventDispatcher() {
const component = get_current_component();
return (type, detail, { cancelable = false } = {}) => {
const callbacks = component.$$.callbacks[type];
if (callbacks) {
// TODO are there situations where events could be dispatched
// in a server (non-DOM) environment?
const event = custom_event(/** @type {string} */ (type), detail, { cancelable });
callbacks.slice().forEach((fn) => {
fn.call(component, event);
});
return !event.defaultPrevented;
}
return true;
};
}
const dirty_components = [];
const binding_callbacks = [];
let render_callbacks = [];
const flush_callbacks = [];
const resolved_promise = /* @__PURE__ */ Promise.resolve();
let update_scheduled = false;
/** @returns {void} */
function schedule_update() {
if (!update_scheduled) {
update_scheduled = true;
resolved_promise.then(flush);
}
}
/** @returns {void} */
function add_render_callback(fn) {
render_callbacks.push(fn);
}
// flush() calls callbacks in this order:
// 1. All beforeUpdate callbacks, in order: parents before children
// 2. All bind:this callbacks, in reverse order: children before parents.
// 3. All afterUpdate callbacks, in order: parents before children. EXCEPT
// for afterUpdates called during the initial onMount, which are called in
// reverse order: children before parents.
// Since callbacks might update component values, which could trigger another
// call to flush(), the following steps guard against this:
// 1. During beforeUpdate, any updated components will be added to the
// dirty_components array and will cause a reentrant call to flush(). Because
// the flush index is kept outside the function, the reentrant call will pick
// up where the earlier call left off and go through all dirty components. The
// current_component value is saved and restored so that the reentrant call will
// not interfere with the "parent" flush() call.
// 2. bind:this callbacks cannot trigger new flush() calls.
// 3. During afterUpdate, any updated components will NOT have their afterUpdate
// callback called a second time; the seen_callbacks set, outside the flush()
// function, guarantees this behavior.
const seen_callbacks = new Set();
let flushidx = 0; // Do *not* move this inside the flush() function
/** @returns {void} */
function flush() {
// Do not reenter flush while dirty components are updated, as this can
// result in an infinite loop. Instead, let the inner flush handle it.
// Reentrancy is ok afterwards for bindings etc.
if (flushidx !== 0) {
return;
}
const saved_component = current_component;
do {
// first, call beforeUpdate functions
// and update components
try {
while (flushidx < dirty_components.length) {
const component = dirty_components[flushidx];
flushidx++;
set_current_component(component);
update(component.$$);
}
} catch (e) {
// reset dirty state to not end up in a deadlocked state and then rethrow
dirty_components.length = 0;
flushidx = 0;
throw e;
}
set_current_component(null);
dirty_components.length = 0;
flushidx = 0;
while (binding_callbacks.length) binding_callbacks.pop()();
// then, once components are updated, call
// afterUpdate functions. This may cause
// subsequent updates...
for (let i = 0; i < render_callbacks.length; i += 1) {
const callback = render_callbacks[i];
if (!seen_callbacks.has(callback)) {
// ...so guard against infinite loops
seen_callbacks.add(callback);
callback();
}
}
render_callbacks.length = 0;
} while (dirty_components.length);
while (flush_callbacks.length) {
flush_callbacks.pop()();
}
update_scheduled = false;
seen_callbacks.clear();
set_current_component(saved_component);
}
/** @returns {void} */
function update($$) {
if ($$.fragment !== null) {
$$.update();
run_all($$.before_update);
const dirty = $$.dirty;
$$.dirty = [-1];
$$.fragment && $$.fragment.p($$.ctx, dirty);
$$.after_update.forEach(add_render_callback);
}
}
/**
* Useful for example to execute remaining `afterUpdate` callbacks before executing `destroy`.
* @param {Function[]} fns
* @returns {void}
*/
function flush_render_callbacks(fns) {
const filtered = [];
const targets = [];
render_callbacks.forEach((c) => (fns.indexOf(c) === -1 ? filtered.push(c) : targets.push(c)));
targets.forEach((c) => c());
render_callbacks = filtered;
}
const outroing = new Set();
/**
* @type {Outro}
*/
let outros;
/**
* @returns {void} */
function group_outros() {
outros = {
r: 0,
c: [],
p: outros // parent group
};
}
/**
* @returns {void} */
function check_outros() {
if (!outros.r) {
run_all(outros.c);
}
outros = outros.p;
}
/**
* @param {import('./private.js').Fragment} block
* @param {0 | 1} [local]
* @returns {void}
*/
function transition_in(block, local) {
if (block && block.i) {
outroing.delete(block);
block.i(local);
}
}
/**
* @param {import('./private.js').Fragment} block
* @param {0 | 1} local
* @param {0 | 1} [detach]
* @param {() => void} [callback]
* @returns {void}
*/
function transition_out(block, local, detach, callback) {
if (block && block.o) {
if (outroing.has(block)) return;
outroing.add(block);
outros.c.push(() => {
outroing.delete(block);
if (callback) {
if (detach) block.d(1);
callback();
}
});
block.o(local);
} else if (callback) {
callback();
}
}
/** @typedef {1} INTRO */
/** @typedef {0} OUTRO */
/** @typedef {{ direction: 'in' | 'out' | 'both' }} TransitionOptions */
/** @typedef {(node: Element, params: any, options: TransitionOptions) => import('../transition/public.js').TransitionConfig} TransitionFn */
/**
* @typedef {Object} Outro
* @property {number} r
* @property {Function[]} c
* @property {Object} p
*/
/**
* @typedef {Object} PendingProgram
* @property {number} start
* @property {INTRO|OUTRO} b
* @property {Outro} [group]
*/
/**
* @typedef {Object} Program
* @property {number} a
* @property {INTRO|OUTRO} b
* @property {1|-1} d
* @property {number} duration
* @property {number} start
* @property {number} end
* @property {Outro} [group]
*/
// general each functions:
function ensure_array_like(array_like_or_iterator) {
return array_like_or_iterator?.length !== undefined
? array_like_or_iterator
: Array.from(array_like_or_iterator);
}
/** @returns {void} */
function create_component(block) {
block && block.c();
}
/** @returns {void} */
function mount_component(component, target, anchor) {
const { fragment, after_update } = component.$$;
fragment && fragment.m(target, anchor);
// onMount happens before the initial afterUpdate
add_render_callback(() => {
const new_on_destroy = component.$$.on_mount.map(run).filter(is_function);
// if the component was destroyed immediately
// it will update the `$$.on_destroy` reference to `null`.
// the destructured on_destroy may still reference to the old array
if (component.$$.on_destroy) {
component.$$.on_destroy.push(...new_on_destroy);
} else {
// Edge case - component was destroyed immediately,
// most likely as a result of a binding initialising
run_all(new_on_destroy);
}
component.$$.on_mount = [];
});
after_update.forEach(add_render_callback);
}
/** @returns {void} */
function destroy_component(component, detaching) {
const $$ = component.$$;
if ($$.fragment !== null) {
flush_render_callbacks($$.after_update);
run_all($$.on_destroy);
$$.fragment && $$.fragment.d(detaching);
// TODO null out other refs, including component.$$ (but need to
// preserve final state?)
$$.on_destroy = $$.fragment = null;
$$.ctx = [];
}
}
/** @returns {void} */
function make_dirty(component, i) {
if (component.$$.dirty[0] === -1) {
dirty_components.push(component);
schedule_update();
component.$$.dirty.fill(0);
}
component.$$.dirty[(i / 31) | 0] |= 1 << i % 31;
}
// TODO: Document the other params
/**
* @param {SvelteComponent} component
* @param {import('./public.js').ComponentConstructorOptions} options
*
* @param {import('./utils.js')['not_equal']} not_equal Used to compare props and state values.
* @param {(target: Element | ShadowRoot) => void} [append_styles] Function that appends styles to the DOM when the component is first initialised.
* This will be the `add_css` function from the compiled component.
*
* @returns {void}
*/
function init(
component,
options,
instance,
create_fragment,
not_equal,
props,
append_styles = null,
dirty = [-1]
) {
const parent_component = current_component;
set_current_component(component);
/** @type {import('./private.js').T$$} */
const $$ = (component.$$ = {
fragment: null,
ctx: [],
// state
props,
update: noop,
not_equal,
bound: blank_object(),
// lifecycle
on_mount: [],
on_destroy: [],
on_disconnect: [],
before_update: [],
after_update: [],
context: new Map(options.context || (parent_component ? parent_component.$$.context : [])),
// everything else
callbacks: blank_object(),
dirty,
skip_bound: false,
root: options.target || parent_component.$$.root
});
append_styles && append_styles($$.root);
let ready = false;
$$.ctx = instance
? instance(component, options.props || {}, (i, ret, ...rest) => {
const value = rest.length ? rest[0] : ret;
if ($$.ctx && not_equal($$.ctx[i], ($$.ctx[i] = value))) {
if (!$$.skip_bound && $$.bound[i]) $$.bound[i](value);
if (ready) make_dirty(component, i);
}
return ret;
})
: [];
$$.update();
ready = true;
run_all($$.before_update);
// `false` as a special case of no DOM component
$$.fragment = create_fragment ? create_fragment($$.ctx) : false;
if (options.target) {
if (options.hydrate) {
// TODO: what is the correct type here?
// @ts-expect-error
const nodes = children(options.target);
$$.fragment && $$.fragment.l(nodes);
nodes.forEach(detach);
} else {
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
$$.fragment && $$.fragment.c();
}
if (options.intro) transition_in(component.$$.fragment);
mount_component(component, options.target, options.anchor);
flush();
}
set_current_component(parent_component);
}
let SvelteElement;
if (typeof HTMLElement === 'function') {
SvelteElement = class extends HTMLElement {
/** The Svelte component constructor */
$$ctor;
/** Slots */
$$s;
/** The Svelte component instance */
$$c;
/** Whether or not the custom element is connected */
$$cn = false;
/** Component props data */
$$d = {};
/** `true` if currently in the process of reflecting component props back to attributes */
$$r = false;
/** @type {Record<string, CustomElementPropDefinition>} Props definition (name, reflected, type etc) */
$$p_d = {};
/** @type {Record<string, Function[]>} Event listeners */
$$l = {};
/** @type {Map<Function, Function>} Event listener unsubscribe functions */
$$l_u = new Map();
constructor($$componentCtor, $$slots, use_shadow_dom) {
super();
this.$$ctor = $$componentCtor;
this.$$s = $$slots;
if (use_shadow_dom) {
this.attachShadow({ mode: 'open' });
}
}
addEventListener(type, listener, options) {
// We can't determine upfront if the event is a custom event or not, so we have to
// listen to both. If someone uses a custom event with the same name as a regular
// browser event, this fires twice - we can't avoid that.
this.$$l[type] = this.$$l[type] || [];
this.$$l[type].push(listener);
if (this.$$c) {
const unsub = this.$$c.$on(type, listener);
this.$$l_u.set(listener, unsub);
}
super.addEventListener(type, listener, options);
}
removeEventListener(type, listener, options) {
super.removeEventListener(type, listener, options);
if (this.$$c) {
const unsub = this.$$l_u.get(listener);
if (unsub) {
unsub();
this.$$l_u.delete(listener);
}
}
}
async connectedCallback() {
this.$$cn = true;
if (!this.$$c) {
// We wait one tick to let possible child slot elements be created/mounted
await Promise.resolve();
if (!this.$$cn) {
return;
}
function create_slot(name) {
return () => {
let node;
const obj = {
c: function create() {
node = element('slot');
if (name !== 'default') {
attr(node, 'name', name);
}
},
/**
* @param {HTMLElement} target
* @param {HTMLElement} [anchor]
*/
m: function mount(target, anchor) {
insert(target, node, anchor);
},
d: function destroy(detaching) {
if (detaching) {
detach(node);
}
}
};
return obj;
};
}
const $$slots = {};
const existing_slots = get_custom_elements_slots(this);
for (const name of this.$$s) {
if (name in existing_slots) {
$$slots[name] = [create_slot(name)];
}
}
for (const attribute of this.attributes) {
// this.$$data takes precedence over this.attributes
const name = this.$$g_p(attribute.name);
if (!(name in this.$$d)) {
this.$$d[name] = get_custom_element_value(name, attribute.value, this.$$p_d, 'toProp');
}
}
// Port over props that were set programmatically before ce was initialized
for (const key in this.$$p_d) {
if (!(key in this.$$d) && this[key] !== undefined) {
this.$$d[key] = this[key]; // don't transform, these were set through JavaScript
delete this[key]; // remove the property that shadows the getter/setter
}
}
this.$$c = new this.$$ctor({
target: this.shadowRoot || this,
props: {
...this.$$d,
$$slots,
$$scope: {
ctx: []
}
}
});
// Reflect component props as attributes
const reflect_attributes = () => {
this.$$r = true;
for (const key in this.$$p_d) {
this.$$d[key] = this.$$c.$$.ctx[this.$$c.$$.props[key]];
if (this.$$p_d[key].reflect) {
const attribute_value = get_custom_element_value(
key,
this.$$d[key],
this.$$p_d,
'toAttribute'
);
if (attribute_value == null) {
this.removeAttribute(this.$$p_d[key].attribute || key);
} else {
this.setAttribute(this.$$p_d[key].attribute || key, attribute_value);
}
}
}
this.$$r = false;
};
this.$$c.$$.after_update.push(reflect_attributes);
reflect_attributes(); // once initially because after_update is added too late for first render
for (const type in this.$$l) {
for (const listener of this.$$l[type]) {
const unsub = this.$$c.$on(type, listener);
this.$$l_u.set(listener, unsub);
}
}
this.$$l = {};
}
}
// We don't need this when working within Svelte code, but for compatibility of people using this outside of Svelte
// and setting attributes through setAttribute etc, this is helpful
attributeChangedCallback(attr, _oldValue, newValue) {
if (this.$$r) return;
attr = this.$$g_p(attr);
this.$$d[attr] = get_custom_element_value(attr, newValue, this.$$p_d, 'toProp');
this.$$c?.$set({ [attr]: this.$$d[attr] });
}
disconnectedCallback() {
this.$$cn = false;
// In a microtask, because this could be a move within the DOM
Promise.resolve().then(() => {
if (!this.$$cn) {
this.$$c.$destroy();
this.$$c = undefined;
}
});
}
$$g_p(attribute_name) {
return (
Object.keys(this.$$p_d).find(
(key) =>
this.$$p_d[key].attribute === attribute_name ||
(!this.$$p_d[key].attribute && key.toLowerCase() === attribute_name)
) || attribute_name
);
}
};
}
/**
* @param {string} prop
* @param {any} value
* @param {Record<string, CustomElementPropDefinition>} props_definition
* @param {'toAttribute' | 'toProp'} [transform]
*/
function get_custom_element_value(prop, value, props_definition, transform) {
const type = props_definition[prop]?.type;
value = type === 'Boolean' && typeof value !== 'boolean' ? value != null : value;
if (!transform || !props_definition[prop]) {
return value;
} else if (transform === 'toAttribute') {
switch (type) {
case 'Object':
case 'Array':
return value == null ? null : JSON.stringify(value);
case 'Boolean':
return value ? '' : null;
case 'Number':
return value == null ? null : value;
default:
return value;
}
} else {
switch (type) {
case 'Object':
case 'Array':
return value && JSON.parse(value);
case 'Boolean':
return value; // conversion already handled above
case 'Number':
return value != null ? +value : value;
default:
return value;
}
}
}
/**
* @internal
*
* Turn a Svelte component into a custom element.
* @param {import('./public.js').ComponentType} Component A Svelte component constructor
* @param {Record<string, CustomElementPropDefinition>} props_definition The props to observe
* @param {string[]} slots The slots to create
* @param {string[]} accessors Other accessors besides the ones for props the component has
* @param {boolean} use_shadow_dom Whether to use shadow DOM
* @param {(ce: new () => HTMLElement) => new () => HTMLElement} [extend]
*/
function create_custom_element(
Component,
props_definition,
slots,
accessors,
use_shadow_dom,
extend
) {
let Class = class extends SvelteElement {
constructor() {
super(Component, slots, use_shadow_dom);
this.$$p_d = props_definition;
}
static get observedAttributes() {
return Object.keys(props_definition).map((key) =>
(props_definition[key].attribute || key).toLowerCase()
);
}
};
Object.keys(props_definition).forEach((prop) => {
Object.defineProperty(Class.prototype, prop, {
get() {
return this.$$c && prop in this.$$c ? this.$$c[prop] : this.$$d[prop];
},
set(value) {
value = get_custom_element_value(prop, value, props_definition);
this.$$d[prop] = value;
this.$$c?.$set({ [prop]: value });
}
});
});
accessors.forEach((accessor) => {
Object.defineProperty(Class.prototype, accessor, {
get() {
return this.$$c?.[accessor];
}
});
});
if (extend) {
// @ts-expect-error - assigning here is fine
Class = extend(Class);
}
Component.element = /** @type {any} */ (Class);
return Class;
}
/**
* Base class for Svelte components. Used when dev=false.
*
* @template {Record<string, any>} [Props=any]
* @template {Record<string, any>} [Events=any]
*/
class SvelteComponent {
/**
* ### PRIVATE API
*
* Do not use, may change at any time
*
* @type {any}
*/
$$ = undefined;
/**
* ### PRIVATE API
*
* Do not use, may change at any time
*
* @type {any}
*/
$$set = undefined;
/** @returns {void} */
$destroy() {
destroy_component(this, 1);
this.$destroy = noop;
}
/**
* @template {Extract<keyof Events, string>} K
* @param {K} type
* @param {((e: Events[K]) => void) | null | undefined} callback
* @returns {() => void}
*/
$on(type, callback) {
if (!is_function(callback)) {
return noop;
}
const callbacks = this.$$.callbacks[type] || (this.$$.callbacks[type] = []);
callbacks.push(callback);
return () => {
const index = callbacks.indexOf(callback);
if (index !== -1) callbacks.splice(index, 1);
};
}
/**
* @param {Partial<Props>} props
* @returns {void}
*/
$set(props) {
if (this.$$set && !is_empty(props)) {
this.$$.skip_bound = true;
this.$$set(props);
this.$$.skip_bound = false;
}
}
}
/**
* @typedef {Object} CustomElementPropDefinition
* @property {string} [attribute]
* @property {boolean} [reflect]
* @property {'String'|'Boolean'|'Number'|'Array'|'Object'} [type]
*/
// generated during release, do not modify
const PUBLIC_VERSION = '4';
if (typeof window !== 'undefined')
// @ts-ignore
(window.__svelte || (window.__svelte = { v: new Set() })).v.add(PUBLIC_VERSION);
const subscriber_queue = [];
/**
* Create a `Writable` store that allows both updating and reading by subscription.
*
* https://svelte.dev/docs/svelte-store#writable
* @template T
* @param {T} [value] initial value
* @param {import('./public.js').StartStopNotifier<T>} [start]
* @returns {import('./public.js').Writable<T>}
*/
function writable(value, start = noop) {
/** @type {import('./public.js').Unsubscriber} */
let stop;
/** @type {Set<import('./private.js').SubscribeInvalidateTuple<T>>} */
const subscribers = new Set();
/** @param {T} new_value
* @returns {void}
*/
function set(new_value) {
if (safe_not_equal(value, new_value)) {
value = new_value;
if (stop) {
// store is ready
const run_queue = !subscriber_queue.length;
for (const subscriber of subscribers) {
subscriber[1]();
subscriber_queue.push(subscriber, value);
}
if (run_queue) {
for (let i = 0; i < subscriber_queue.length; i += 2) {
subscriber_queue[i][0](subscriber_queue[i + 1]);
}
subscriber_queue.length = 0;
}
}
}
}
/**
* @param {import('./public.js').Updater<T>} fn
* @returns {void}
*/
function update(fn) {
set(fn(value));
}
/**
* @param {import('./public.js').Subscriber<T>} run
* @param {import('./private.js').Invalidator<T>} [invalidate]
* @returns {import('./public.js').Unsubscriber}
*/
function subscribe(run, invalidate = noop) {
/** @type {import('./private.js').SubscribeInvalidateTuple<T>} */
const subscriber = [run, invalidate];
subscribers.add(subscriber);
if (subscribers.size === 1) {
stop = start(set, update) || noop;
}
run(value);
return () => {
subscribers.delete(subscriber);
if (subscribers.size === 0 && stop) {
stop();
stop = null;
}
};
}
return { set, update, subscribe };
}
/**
* @param {any} obj
* @returns {boolean}
*/
function is_date(obj) {
return Object.prototype.toString.call(obj) === '[object Date]';
}
/**
* @template T
* @param {import('./private.js').TickContext<T>} ctx
* @param {T} last_value
* @param {T} current_value
* @param {T} target_value
* @returns {T}
*/
function tick_spring(ctx, last_value, current_value, target_value) {
if (typeof current_value === 'number' || is_date(current_value)) {
// @ts-ignore
const delta = target_value - current_value;
// @ts-ignore
const velocity = (current_value - last_value) / (ctx.dt || 1 / 60); // guard div by 0
const spring = ctx.opts.stiffness * delta;
const damper = ctx.opts.damping * velocity;
const acceleration = (spring - damper) * ctx.inv_mass;
const d = (velocity + acceleration) * ctx.dt;
if (Math.abs(d) < ctx.opts.precision && Math.abs(delta) < ctx.opts.precision) {
return target_value; // settled
} else {
ctx.settled = false; // signal loop to keep ticking
// @ts-ignore
return is_date(current_value) ? new Date(current_value.getTime() + d) : current_value + d;
}
} else if (Array.isArray(current_value)) {
// @ts-ignore
return current_value.map((_, i) =>
tick_spring(ctx, last_value[i], current_value[i], target_value[i])
);
} else if (typeof current_value === 'object') {
const next_value = {};
for (const k in current_value) {
// @ts-ignore
next_value[k] = tick_spring(ctx, last_value[k], current_value[k], target_value[k]);
}
// @ts-ignore
return next_value;
} else {
throw new Error(`Cannot spring ${typeof current_value} values`);
}
}
/**
* The spring function in Svelte creates a store whose value is animated, with a motion that simulates the behavior of a spring. This means when the value changes, instead of transitioning at a steady rate, it "bounces" like a spring would, depending on the physics parameters provided. This adds a level of realism to the transitions and can enhance the user experience.
*
* https://svelte.dev/docs/svelte-motion#spring
* @template [T=any]
* @param {T} [value]
* @param {import('./private.js').SpringOpts} [opts]
* @returns {import('./public.js').Spring<T>}
*/
function spring(value, opts = {}) {
const store = writable(value);
const { stiffness = 0.15, damping = 0.8, precision = 0.01 } = opts;
/** @type {number} */
let last_time;
/** @type {import('../internal/private.js').Task} */
let task;
/** @type {object} */
let current_token;
/** @type {T} */
let last_value = value;
/** @type {T} */
let target_value = value;
let inv_mass = 1;
let inv_mass_recovery_rate = 0;
let cancel_task = false;
/**
* @param {T} new_value
* @param {import('./private.js').SpringUpdateOpts} opts
* @returns {Promise<void>}
*/
function set(new_value, opts = {}) {
target_value = new_value;
const token = (current_token = {});
if (value == null || opts.hard || (spring.stiffness >= 1 && spring.damping >= 1)) {
cancel_task = true; // cancel any running animation
last_time = now();
last_value = new_value;
store.set((value = target_value));
return Promise.resolve();
} else if (opts.soft) {
const rate = opts.soft === true ? 0.5 : +opts.soft;
inv_mass_recovery_rate = 1 / (rate * 60);
inv_mass = 0; // infinite mass, unaffected by spring forces
}
if (!task) {
last_time = now();
cancel_task = false;
task = loop((now) => {
if (cancel_task) {
cancel_task = false;
task = null;
return false;
}
inv_mass = Math.min(inv_mass + inv_mass_recovery_rate, 1);
const ctx = {
inv_mass,
opts: spring,
settled: true,
dt: ((now - last_time) * 60) / 1000
};
const next_value = tick_spring(ctx, last_value, value, target_value);
last_time = now;
last_value = value;
store.set((value = next_value));
if (ctx.settled) {
task = null;
}
return !ctx.settled;
});
}
return new Promise((fulfil) => {
task.promise.then(() => {
if (token === current_token) fulfil();
});
});
}
/** @type {import('./public.js').Spring<T>} */
const spring = {
set,
update: (fn, opts) => set(fn(target_value, value), opts),
subscribe: store.subscribe,
stiffness,
damping,
precision
};
return spring;
}
/**
* make sure the value is coerced to a float value
* @param {number|string} value the value to fix
* @param {number} precision the number of decimal places to fix to
* @return {number} a float version of the input
**/
const coerceFloat = (value, precision = 2) => {
return parseFloat((+value).toFixed(precision));
};
/**
* clamp a value from a range so that it always
* falls within the min/max values
* @param {number} value the value to clamp
* @param {number} min the minimum value
* @param {number} max the maximum value
* @return {number} the value after it's been clamped
**/
const clampValue = function (value, min, max) {
// return the min/max if outside of that range
return value <= min ? min : value >= max ? max : value;
};
/**
* take in a value, and then calculate that value's percentage
* of the overall range (min-max);
* @param {number} value the value we're getting percent for
* @param {number} min the minimum value
* @param {number} max the maximum value
* @param {number} precision the number of decimal places to fix to (default 2)
* @return {number} the percentage value
**/
const valueAsPercent = function (value, min, max, precision = 2) {
let percent = ((value - min) / (max - min)) * 100;
if (isNaN(percent) || percent <= 0) {
return 0;
}
else if (percent >= 100) {
return 100;
}
else {
return coerceFloat(percent, precision);
}
};
/**
* align the value with the steps so that it
* always sits on the closest (above/below) step
* @param {number} value the value to align
* @param {number} min the minimum value
* @param {number} max the maximum value
* @param {number} step the step value
* @param {number} precision the number of decimal places to fix to
* @return {number} the value after it's been aligned
**/
const alignValueToStep = function (value, min, max, step, precision = 2, limits = null) {
// if limits are provided, clamp the value between the limits
// if no limits are provided, clamp the value between the min and max
value = clampValue(value, limits?.[0] ?? min, limits?.[1] ?? max);
// find the middle-point between steps
// and see if the value is closer to the
// next step, or previous step
let remainder = (value - min) % step;
let aligned = value - remainder;
if (Math.abs(remainder) * 2 >= step) {
aligned += remainder > 0 ? step : -step;
}
// make sure the value is within acceptable limits
aligned = clampValue(aligned, limits?.[0] ?? min, limits?.[1] ?? max);
// make sure the returned value is set to the precision desired
// this is also because javascript often returns weird floats
// when dealing with odd numbers and percentages
return coerceFloat(aligned, precision);
};
/**
* helper to take a string of html and return only the text
* @param {string} possibleHtml the string that may contain html
* @return {string} the text from the input
*/
const pureText = (possibleHtml = '') => {
return `${possibleHtml}`.replace(/<[^>]*>/g, '');
};
/**
* normalise a mouse or touch event to return the
* client (x/y) object for that event
* @param {event} event a mouse/touch event to normalise
* @returns {object} normalised event client object (x,y)
**/
const normalisedClient = (event) => {
const { clientX, clientY } = 'touches' in event ? event.touches[0] || event.changedTouches[0] : event;
return { x: clientX, y: clientY };
};
/**
* helper func to get the index of an element in it's DOM container
* @param {Element} el dom object reference we want the index of
* @returns {number} the index of the input element
**/
const elementIndex = (el) => {
if (!el)
return -1;
var i = 0;
while ((el = el.previousElementSibling)) {
i++;
}
return i;
};
/**
* helper to check if the given value is inside the range
* @param value the value to check if is in the range
* @param range the range of values to check against
* @param type the type of range to check against
* @returns {boolean} true if the value is in the range
*/
const isInRange = (value, range, type) => {
if (type === 'min') {
// if the range is 'min', then we're checking if the value is above the min value
return range[0] > value;
}
else if (type === 'max') {
// if the range is 'max', then we're checking if the value is below the max value
return range[0] < value;
}
else if (type) {
// if the range is a boolean of true, then we're checking if the value is in the range
return range[0] < value && range[1] > value;
}
};
const isOutOfLimit = (value, limits) => {
if (!limits)
return false;
return value < limits[0] || value > limits[1];
};
/**
* helper to check if the given value is selected
* @param value the value to check if is selected
* @param values the values to check against
* @param precision the precision to check against
* @returns {boolean} true if the value is selected
*/
const isSelected = (value, values, precision = 2) => {
return values.some((v) => coerceFloat(v, precision) === coerceFloat(value, precision));
};
/**
* helper to return the value of a pip based on the index, and the min/max values,
* and the step of the range slider
* @param index the index of the pip
* @param min the minimum value of the range slider
* @param max the maximum value of the range slider
* @param pipStep the step of the pips
* @param step the step of the range slider
* @param precision the precision to check against
* @returns {number} the value of the pip
*/
const getValueFromIndex = (index, min, max, pipStep, step, precision = 2) => {
return coerceFloat(min + index * step * pipStep, precision);
};
/* src/lib/components/RangePips.svelte generated by Svelte v4.2.9 */
function add_css$1(target) {
append_styles(target, "svelte-1k57hry", ".rangePips{--pip:var(--range-pip, lightslategray);--pip-text:var(--range-pip-text, var(--pip));--pip-active:var(--range-pip-active, darkslategrey);--pip-active-text:var(--range-pip-active-text, var(--pip-active));--pip-hover:var(--range-pip-hover, darkslategrey);--pip-hover-text:var(--range-pip-hover-text, var(--pip-hover));--pip-in-range:var(--range-pip-in-range, var(--pip-active));--pip-in-range-text:var(--range-pip-in-range-text, var(--pip-active-text));--pip-out-of-limit:var(--range-pip-out-of-limit, #aebecf);--pip-out-of-limit-text:var(--range-pip-out-of-limit-text, var(--pip-out-of-limit))}.rangePips{position:absolute;height:1em;left:0;right:0;bottom:-1em;font-variant-numeric:tabular-nums}.rangePips.vertical{height:auto;width:1em;left:100%;right:auto;top:0;bottom:0}.rangePips .pip{height:0.4em;position:absolute;top:0.25em;width:1px;white-space:nowrap}.rangePips.vertical .pip{height:1px;width:0.4em;left:0.25em;top:auto;bottom:auto}.rangePips .pipVal{position:absolute;top:0.4em;transform:translate(-50%, 25%);display:inline-flex}.rangePips.vertical .pipVal{position:absolute;top:0;left:0.4em;transform:translate(25%, -50%)}.rangePips .pip{transition:all 0.15s ease}.rangePips .pipVal{transition:all 0.15s ease,\n font-weight 0s linear}.rangePips .pip{color:lightslategray;color:var(--pip-text);background-color:lightslategray;background-color:var(--pip)}.rangePips .pip.selected{color:darkslategrey;color:var(--pip-active-text);background-color:darkslategrey;background-color:var(--pip-active)}.rangePips.hoverable:not(.disabled) .pip:not(.out-of-limit):hover{color:darkslategrey;color:var(--pip-hover-text);background-color:darkslategrey;background-color:var(--pip-hover)}.rangePips .pip.in-range{color:darkslategrey;color:var(--pip-in-range-text);background-color:darkslategrey;background-color:var(--pip-in-range)}.rangePips .pip.out-of-limit{color:#aebecf;color:var(--pip-out-of-limit-text);background-color:#aebecf;background-color:var(--pip-out-of-limit)}.rangePips .pip.selected{height:0.75em}.rangePips.vertical .pip.selected{height:1px;width:0.75em}.rangePips .pip.selected .pipVal{font-weight:bold;top:0.75em}.rangePips.vertical .pip.selected .pipVal{top:0;left:0.75em}.rangePips.hoverable:not(.disabled) .pip:not(.selected):not(.out-of-limit):hover{transition:none}\n .rangePips.hoverable:not(.disabled) .pip:not(.selected):not(.out-of-limit):hover .pipVal\n {transition:none;font-weight:bold}");
}
function get_each_context$1(ctx, list, i) {
const child_ctx = ctx.slice();
child_ctx[38] = list[i];
child_ctx[41] = i;
const constants_0 = getValueFromIndex(/*i*/ child_ctx[41], /*min*/ child_ctx[1], /*max*/ child_ctx[2], /*pipStep*/ child_ctx[20], /*step*/ child_ctx[3], /*precision*/ child_ctx[17]);
child_ctx[39] = constants_0;
return child_ctx;
}
// (65:2) {#if (all && first !== false) || first}
function create_if_block_9(ctx) {
let span;
let span_style_value;
let span_data_val_value;
let mounted;
let dispose;
let if_block = (/*all*/ ctx[10] === 'label' || /*first*/ ctx[11] === 'label') && create_if_block_10(ctx);
return {
c() {
span = element("span");
if (if_block) if_block.c();
attr(span, "class", "pip first");
attr(span, "style", span_style_value = "" + (/*orientationStart*/ ctx[19] + ": 0%;"));
attr(span, "data-val", span_data_val_value = coerceFloat(/*min*/ ctx[1], /*precision*/ ctx[17]));
toggle_class(span, "selected", isSelected(/*min*/ ctx[1], /*values*/ ctx[4], /*precision*/ ctx[17]));
toggle_class(span, "in-range", isInRange(/*min*/ ctx[1], /*values*/ ctx[4], /*range*/ ctx[0]));
toggle_class(span, "out-of-limit", isOutOfLimit(/*min*/ ctx[1], /*limits*/ ctx[9]));
},
m(target, anchor) {
insert(target, span, anchor);
if (if_block) if_block.m(span, null);
if (!mounted) {
dispose = [
listen(span, "pointerdown", /*pointerdown_handler*/ ctx[31]),
listen(span, "pointerup", /*pointerup_handler*/ ctx[32])
];
mounted = true;
}
},
p(ctx, dirty) {
if (/*all*/ ctx[10] === 'label' || /*first*/ ctx[11] === 'label') {
if (if_block) {
if_block.p(ctx, dirty);
} else {
if_block = create_if_block_10(ctx);
if_block.c();
if_block.m(span, null);
}
} else if (if_block) {
if_block.d(1);
if_block = null;
}
if (dirty[0] & /*orientationStart*/ 524288 && span_style_value !== (span_style_value = "" + (/*orientationStart*/ ctx[19] + ": 0%;"))) {
attr(span, "style", span_style_value);
}
if (dirty[0] & /*min, precision*/ 131074 && span_data_val_value !== (span_data_val_value = coerceFloat(/*min*/ ctx[1], /*precision*/ ctx[17]))) {
attr(span, "data-val", span_data_val_value);
}
if (dirty[0] & /*min, values, precision*/ 131090) {
toggle_class(span, "selected", isSelected(/*min*/ ctx[1], /*values*/ ctx[4], /*precision*/ ctx[17]));
}
if (dirty[0] & /*min, values, range*/ 19) {
toggle_class(span, "in-range", isInRange(/*min*/ ctx[1], /*values*/ ctx[4], /*range*/ ctx[0]));
}
if (dirty[0] & /*min, limits*/ 514) {
toggle_class(span, "out-of-limit", isOutOfLimit(/*min*/ ctx[1], /*limits*/ ctx[9]));
}
},
d(detaching) {
if (detaching) {
detach(span);
}
if (if_block) if_block.d();
mounted = false;
run_all(dispose);
}
};
}
// (80:6) {#if all === 'label' || first === 'label'}
function create_if_block_10(ctx) {
let span;
let t0;
let html_tag;
let raw_value = /*formatter*/ ctx[16](coerceFloat(/*min*/ ctx[1], /*precision*/ ctx[17]), 0, 0) + "";
let t1;
let if_block0 = /*prefix*/ ctx[14] && create_if_block_12(ctx);
let if_block1 = /*suffix*/ ctx[15] && create_if_block_11(ctx);
return {
c() {
span = element("span");
if (if_block0) if_block0.c();
t0 = space();
html_tag = new HtmlTag(false);
t1 = space();
if (if_block1) if_block1.c();
html_tag.a = t1;
attr(span, "class", "pipVal");
},
m(target, anchor) {
insert(target, span, anchor);
if (if_block0) if_block0.m(span, null);
append(span, t0);
html_tag.m(raw_value, span);
append(span, t1);
if (if_block1) if_block1.m(span, null);
},
p(ctx, dirty) {
if (/*prefix*/ ctx[14]) {
if (if_block0) {
if_block0.p(ctx, dirty);
} else {
if_block0 = create_if_block_12(ctx);
if_block0.c();
if_block0.m(span, t0);
}
} else if (if_block0) {
if_block0.d(1);
if_block0 = null;
}
if (dirty[0] & /*formatter, min, precision*/ 196610 && raw_value !== (raw_value = /*formatter*/ ctx[16](coerceFloat(/*min*/ ctx[1], /*precision*/ ctx[17]), 0, 0) + "")) html_tag.p(raw_value);
if (/*suffix*/ ctx[15]) {
if (if_block1) {
if_block1.p(ctx, dirty);
} else {
if_block1 = create_if_block_11(ctx);
if_block1.c();
if_block1.m(span, null);
}
} else if (if_block1) {
if_block1.d(1);
if_block1 = null;
}
},
d(detaching) {
if (detaching) {
detach(span);
}
if (if_block0) if_block0.d();
if (if_block1) if_block1.d();
}
};
}
// (82:10) {#if prefix}
function create_if_block_12(ctx) {
let span;
let t;
return {
c() {
span = element("span");
t = text(/*prefix*/ ctx[14]);
attr(span, "class", "pipVal-prefix");
},
m(target, anchor) {
insert(target, span, anchor);
append(span, t);
},
p(ctx, dirty) {
if (dirty[0] & /*prefix*/ 16384) set_data(t, /*prefix*/ ctx[14]);
},
d(detaching) {
if (detaching) {
detach(span);
}
}
};
}
// (84:10) {#if suffix}
function create_if_block_11(ctx) {
let span;
let t;
return {
c() {
span = element("span");
t = text(/*suffix*/ ctx[15]);
attr(span, "class", "pipVal-suffix");
},
m(target, anchor) {
insert(target, span, anchor);
append(span, t);
},
p(ctx, dirty) {
if (dirty[0] & /*suffix*/ 32768) set_data(t, /*suffix*/ ctx[15]);
},
d(detaching) {
if (detaching) {
detach(span);
}
}
};
}
// (90:2) {#if (all && rest !== false) || rest}
function create_if_block_4$1(ctx) {
let each_1_anchor;
let each_value = ensure_array_like(Array(/*pipCount*/ ctx[21] + 1));
let each_blocks = [];
for (let i = 0; i < each_value.length; i += 1) {