utils.js
1 /** @returns {void} */ 2 export function noop() {} 3 4 export const identity = (x) => x; 5 6 /** 7 * @template T 8 * @template S 9 * @param {T} tar 10 * @param {S} src 11 * @returns {T & S} 12 */ 13 export function assign(tar, src) { 14 // @ts-ignore 15 for (const k in src) tar[k] = src[k]; 16 return /** @type {T & S} */ (tar); 17 } 18 19 // Adapted from https://github.com/then/is-promise/blob/master/index.js 20 // Distributed under MIT License https://github.com/then/is-promise/blob/master/LICENSE 21 /** 22 * @param {any} value 23 * @returns {value is PromiseLike<any>} 24 */ 25 export function is_promise(value) { 26 return ( 27 !!value && 28 (typeof value === 'object' || typeof value === 'function') && 29 typeof (/** @type {any} */ (value).then) === 'function' 30 ); 31 } 32 33 /** @returns {void} */ 34 export function add_location(element, file, line, column, char) { 35 element.__svelte_meta = { 36 loc: { file, line, column, char } 37 }; 38 } 39 40 export function run(fn) { 41 return fn(); 42 } 43 44 export function blank_object() { 45 return Object.create(null); 46 } 47 48 /** 49 * @param {Function[]} fns 50 * @returns {void} 51 */ 52 export function run_all(fns) { 53 fns.forEach(run); 54 } 55 56 /** 57 * @param {any} thing 58 * @returns {thing is Function} 59 */ 60 export function is_function(thing) { 61 return typeof thing === 'function'; 62 } 63 64 /** @returns {boolean} */ 65 export function safe_not_equal(a, b) { 66 return a != a ? b == b : a !== b || (a && typeof a === 'object') || typeof a === 'function'; 67 } 68 69 let src_url_equal_anchor; 70 71 /** 72 * @param {string} element_src 73 * @param {string} url 74 * @returns {boolean} 75 */ 76 export function src_url_equal(element_src, url) { 77 if (element_src === url) return true; 78 if (!src_url_equal_anchor) { 79 src_url_equal_anchor = document.createElement('a'); 80 } 81 // This is actually faster than doing URL(..).href 82 src_url_equal_anchor.href = url; 83 return element_src === src_url_equal_anchor.href; 84 } 85 86 /** @param {string} srcset */ 87 function split_srcset(srcset) { 88 return srcset.split(',').map((src) => src.trim().split(' ').filter(Boolean)); 89 } 90 91 /** 92 * @param {HTMLSourceElement | HTMLImageElement} element_srcset 93 * @param {string | undefined | null} srcset 94 * @returns {boolean} 95 */ 96 export function srcset_url_equal(element_srcset, srcset) { 97 const element_urls = split_srcset(element_srcset.srcset); 98 const urls = split_srcset(srcset || ''); 99 100 return ( 101 urls.length === element_urls.length && 102 urls.every( 103 ([url, width], i) => 104 width === element_urls[i][1] && 105 // We need to test both ways because Vite will create an a full URL with 106 // `new URL(asset, import.meta.url).href` for the client when `base: './'`, and the 107 // relative URLs inside srcset are not automatically resolved to absolute URLs by 108 // browsers (in contrast to img.src). This means both SSR and DOM code could 109 // contain relative or absolute URLs. 110 (src_url_equal(element_urls[i][0], url) || src_url_equal(url, element_urls[i][0])) 111 ) 112 ); 113 } 114 115 /** @returns {boolean} */ 116 export function not_equal(a, b) { 117 return a != a ? b == b : a !== b; 118 } 119 120 /** @returns {boolean} */ 121 export function is_empty(obj) { 122 return Object.keys(obj).length === 0; 123 } 124 125 /** @returns {void} */ 126 export function validate_store(store, name) { 127 if (store != null && typeof store.subscribe !== 'function') { 128 throw new Error(`'${name}' is not a store with a 'subscribe' method`); 129 } 130 } 131 132 export function subscribe(store, ...callbacks) { 133 if (store == null) { 134 for (const callback of callbacks) { 135 callback(undefined); 136 } 137 return noop; 138 } 139 const unsub = store.subscribe(...callbacks); 140 return unsub.unsubscribe ? () => unsub.unsubscribe() : unsub; 141 } 142 143 /** 144 * Get the current value from a store by subscribing and immediately unsubscribing. 145 * 146 * https://svelte.dev/docs/svelte-store#get 147 * @template T 148 * @param {import('../store/public.js').Readable<T>} store 149 * @returns {T} 150 */ 151 export function get_store_value(store) { 152 let value; 153 subscribe(store, (_) => (value = _))(); 154 return value; 155 } 156 157 /** @returns {void} */ 158 export function component_subscribe(component, store, callback) { 159 component.$$.on_destroy.push(subscribe(store, callback)); 160 } 161 162 export function create_slot(definition, ctx, $$scope, fn) { 163 if (definition) { 164 const slot_ctx = get_slot_context(definition, ctx, $$scope, fn); 165 return definition[0](slot_ctx); 166 } 167 } 168 169 function get_slot_context(definition, ctx, $$scope, fn) { 170 return definition[1] && fn ? assign($$scope.ctx.slice(), definition[1](fn(ctx))) : $$scope.ctx; 171 } 172 173 export function get_slot_changes(definition, $$scope, dirty, fn) { 174 if (definition[2] && fn) { 175 const lets = definition[2](fn(dirty)); 176 if ($$scope.dirty === undefined) { 177 return lets; 178 } 179 if (typeof lets === 'object') { 180 const merged = []; 181 const len = Math.max($$scope.dirty.length, lets.length); 182 for (let i = 0; i < len; i += 1) { 183 merged[i] = $$scope.dirty[i] | lets[i]; 184 } 185 return merged; 186 } 187 return $$scope.dirty | lets; 188 } 189 return $$scope.dirty; 190 } 191 192 /** @returns {void} */ 193 export function update_slot_base( 194 slot, 195 slot_definition, 196 ctx, 197 $$scope, 198 slot_changes, 199 get_slot_context_fn 200 ) { 201 if (slot_changes) { 202 const slot_context = get_slot_context(slot_definition, ctx, $$scope, get_slot_context_fn); 203 slot.p(slot_context, slot_changes); 204 } 205 } 206 207 /** @returns {void} */ 208 export function update_slot( 209 slot, 210 slot_definition, 211 ctx, 212 $$scope, 213 dirty, 214 get_slot_changes_fn, 215 get_slot_context_fn 216 ) { 217 const slot_changes = get_slot_changes(slot_definition, $$scope, dirty, get_slot_changes_fn); 218 update_slot_base(slot, slot_definition, ctx, $$scope, slot_changes, get_slot_context_fn); 219 } 220 221 /** @returns {any[] | -1} */ 222 export function get_all_dirty_from_scope($$scope) { 223 if ($$scope.ctx.length > 32) { 224 const dirty = []; 225 const length = $$scope.ctx.length / 32; 226 for (let i = 0; i < length; i++) { 227 dirty[i] = -1; 228 } 229 return dirty; 230 } 231 return -1; 232 } 233 234 /** @returns {{}} */ 235 export function exclude_internal_props(props) { 236 const result = {}; 237 for (const k in props) if (k[0] !== '$') result[k] = props[k]; 238 return result; 239 } 240 241 /** @returns {{}} */ 242 export function compute_rest_props(props, keys) { 243 const rest = {}; 244 keys = new Set(keys); 245 for (const k in props) if (!keys.has(k) && k[0] !== '$') rest[k] = props[k]; 246 return rest; 247 } 248 249 /** @returns {{}} */ 250 export function compute_slots(slots) { 251 const result = {}; 252 for (const key in slots) { 253 result[key] = true; 254 } 255 return result; 256 } 257 258 /** @returns {(this: any, ...args: any[]) => void} */ 259 export function once(fn) { 260 let ran = false; 261 return function (...args) { 262 if (ran) return; 263 ran = true; 264 fn.call(this, ...args); 265 }; 266 } 267 268 export function null_to_empty(value) { 269 return value == null ? '' : value; 270 } 271 272 export function set_store_value(store, ret, value) { 273 store.set(value); 274 return ret; 275 } 276 277 export const has_prop = (obj, prop) => Object.prototype.hasOwnProperty.call(obj, prop); 278 279 export function action_destroyer(action_result) { 280 return action_result && is_function(action_result.destroy) ? action_result.destroy : noop; 281 } 282 283 /** @param {number | string} value 284 * @returns {[number, string]} 285 */ 286 export function split_css_unit(value) { 287 const split = typeof value === 'string' && value.match(/^\s*(-?[\d.]+)([^\s]*)\s*$/); 288 return split ? [parseFloat(split[1]), split[2] || 'px'] : [/** @type {number} */ (value), 'px']; 289 } 290 291 export const contenteditable_truthy_values = ['', true, 1, 'true', 'contenteditable'];