1// Copyright (C) 2023 The Android Open Source Project 2// 3// Licensed under the Apache License, Version 2.0 (the "License"); 4// you may not use this file except in compliance with the License. 5// You may obtain a copy of the License at 6// 7// http://www.apache.org/licenses/LICENSE-2.0 8// 9// Unless required by applicable law or agreed to in writing, software 10// distributed under the License is distributed on an "AS IS" BASIS, 11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12// See the License for the specific language governing permissions and 13// limitations under the License. 14 15// Return true if value is not nullish - i.e. not null or undefined 16// Allows doing the following 17// exists(val) && m('div', val) 18// Even if val is a non-nullish falsey value like 0 or '' 19export function exists<T>(value: T): value is NonNullable<T> { 20 return value !== undefined && value !== null; 21} 22 23// Type util to make sure that exactly one of the passed keys is defined. 24// Example usage: 25// type FooOrBar = ExactlyOne<{foo: number; bar: number}>; 26// const x : FooOrBar = {foo: 42}; // OK 27// const x : FooOrBar = {bar: 42}; // OK 28// const x : FooOrBar = {}; // Compiler error 29// const x : FooOrBar = {foo:1, bar:2}; // Compiler error 30export type ExactlyOne<T, K extends keyof T = keyof T> = K extends keyof T 31 ? {[P in K]: T[P]} & {[P in Exclude<keyof T, K>]?: undefined} 32 : never; 33 34// Escape characters that are not allowed inside a css selector 35export function escapeCSSSelector(selector: string): string { 36 return selector.replace(/([!"#$%&'()*+,.\/:;<=>?@[\\\]^`{|}~])/g, '\\$1'); 37} 38 39// Make field K required in T 40export type RequiredField<T, K extends keyof T> = Omit<T, K> & 41 Required<Pick<T, K>>; 42 43// The lowest common denoninator between Map<> and WeakMap<>. 44// This is just to avoid duplication of the getOrCreate below. 45interface MapLike<K, V> { 46 get(key: K): V | undefined; 47 set(key: K, value: V): this; 48} 49 50export function getOrCreate<K, V>( 51 map: MapLike<K, V>, 52 key: K, 53 factory: () => V, 54): V { 55 let value = map.get(key); 56 if (value !== undefined) return value; 57 value = factory(); 58 map.set(key, value); 59 return value; 60} 61 62// Allows to take an existing class instance (`target`) and override some of its 63// methods via `overrides`. We use this for cases where we want to expose a 64// "manager" (e.g. TrackManager, SidebarManager) to the plugins, but we want to 65// override few of its methods (e.g. to inject the pluginId in the args). 66export function createProxy<T extends object>( 67 target: T, 68 overrides: Partial<T>, 69): T { 70 return new Proxy(target, { 71 get: (target: T, prop: string | symbol, receiver) => { 72 // If the property is overriden, use that; otherwise, use target 73 const overrideValue = (overrides as {[key: symbol | string]: {}})[prop]; 74 if (overrideValue !== undefined) { 75 return typeof overrideValue === 'function' 76 ? overrideValue.bind(overrides) 77 : overrideValue; 78 } 79 const baseValue = Reflect.get(target, prop, receiver); 80 return typeof baseValue === 'function' 81 ? baseValue.bind(target) 82 : baseValue; 83 }, 84 }) as T; 85} 86