diff options
Diffstat (limited to 'src/lib')
-rw-r--r-- | src/lib/autoComplete.ts | 114 | ||||
-rw-r--r-- | src/lib/button.ts | 29 | ||||
-rw-r--r-- | src/lib/contextMenu.ts | 35 | ||||
-rw-r--r-- | src/lib/fontAwesome.ts | 788 | ||||
-rw-r--r-- | src/lib/form.ts | 80 | ||||
-rw-r--r-- | src/lib/h.ts | 41 | ||||
-rw-r--r-- | src/lib/layout.ts | 15 | ||||
-rw-r--r-- | src/lib/modal.ts | 28 |
8 files changed, 1130 insertions, 0 deletions
diff --git a/src/lib/autoComplete.ts b/src/lib/autoComplete.ts new file mode 100644 index 0000000..769f617 --- /dev/null +++ b/src/lib/autoComplete.ts @@ -0,0 +1,114 @@ +import { h, Children, concatClassName } from 'lib/h' +import * as Button from 'lib/button' + +export function create( + attrs: object, + id: string, + values: string[], + renderEntry: (entry: string) => Element, + onInput: (value: string) => void +): Element { + const completion = h('div', {}) + + const updateCompletion = (target: EventTarget, value: string) => { + const entries = search(value, values) + mountOn( + completion, + renderCompletion( + renderEntry, + selected => { + (target as HTMLInputElement).value = selected + completion.remove + removeChildren(completion) + onInput(selected) + }, + entries + ) + ) + } + + const input = h('input', + concatClassName( + { ...attrs, + id, + autocomplete: 'off', + onfocus: (e: Event) => { + if (e.target !== null) { + const target = e.target as HTMLInputElement + updateCompletion(target, target.value) + } + }, + oninput: (e: Event) => { + if (e.target !== null) { + const target = e.target as HTMLInputElement + updateCompletion(target, target.value) + onInput(target.value) + } + } + }, + 'g-AutoComplete__Input' + ) + ) as HTMLInputElement + + input.addEventListener('blur', (e: MouseEvent) => { + if (e.relatedTarget === null) { + removeChildren(completion) + } + }) + + return h('div', + { className: 'g-AutoComplete' }, + input, + completion, + Button.raw( + { className: 'g-AutoComplete__Clear fa fa-close', + type: 'button', + onclick: () => { + onInput('') + input.value = '' + input.focus() + } + } + ) + ) +} + +function renderCompletion( + renderEntry: (entry: string) => Element, + onSelect: (entry: string) => void, + entries: string[] +): Element { + return h('div', + { className: 'g-AutoComplete__Completion' }, + ...entries.map(c => + Button.raw( + { className: 'g-AutoComplete__Entry', + type: 'button', + onclick: (e: Event) => { + e.stopPropagation() + e.preventDefault() + onSelect(c) + } + }, + renderEntry(c) + ) + ) + ) +} + +function search(s: string, xs: string[]): string[] { + return xs.filter(x => x.includes(s)) +} + +function mountOn(base: Element, ...children: Element[]) { + removeChildren(base) + children.forEach(child => base.appendChild(child)) +} + +function removeChildren(base: Element) { + const firstChild = base.firstChild + if (firstChild !== null) { + base.removeChild(firstChild) + removeChildren(base) + } +} diff --git a/src/lib/button.ts b/src/lib/button.ts new file mode 100644 index 0000000..794df35 --- /dev/null +++ b/src/lib/button.ts @@ -0,0 +1,29 @@ +import { h, Children, concatClassName } from 'lib/h' + +export function raw(attrs: object, ...children: Children): Element { + return h('button', + concatClassName(attrs, 'g-Button__Raw'), + ...children + ) +} + +export function text(attrs: object, ...children: Children): Element { + return h('button', + concatClassName(attrs, 'g-Button__Text'), + ...children + ) +} + +export function action(attrs: object, ...children: Children): Element { + return h('button', + concatClassName(attrs, 'g-Button__Action'), + ...children + ) +} + +export function cancel(attrs: object, ...children: Children): Element { + return h('button', + concatClassName(attrs, 'g-Button__Cancel'), + ...children + ) +} diff --git a/src/lib/contextMenu.ts b/src/lib/contextMenu.ts new file mode 100644 index 0000000..6edd567 --- /dev/null +++ b/src/lib/contextMenu.ts @@ -0,0 +1,35 @@ +import { h } from 'lib/h' + +interface Action { + label: string, + action: () => void +} + +export function show(event: MouseEvent, actions: Action[]) { + const menu = h('div', + { id: 'g-ContextMenu', + style: `left: ${event.pageX.toString()}px; top: ${event.pageY.toString()}px` + }, + ...actions.map(({ label, action }) => + h('div', + { className: 'g-ContextMenu__Entry', + onclick: () => action() + }, + label + ) + ) + ) + + document.body.appendChild(menu) + + // Remove on click or context menu + setTimeout(() => { + const f = () => { + document.body.removeChild(menu) + document.body.removeEventListener('click', f) + document.body.removeEventListener('contextmenu', f) + } + document.body.addEventListener('click', f) + document.body.addEventListener('contextmenu', f) + }, 0) +} diff --git a/src/lib/fontAwesome.ts b/src/lib/fontAwesome.ts new file mode 100644 index 0000000..896fa52 --- /dev/null +++ b/src/lib/fontAwesome.ts @@ -0,0 +1,788 @@ +export const icons: string [] = [ + "500px", + "address-book", + "address-book-o", + "address-card", + "address-card-o", + "adjust", + "adn", + "align-center", + "align-justify", + "align-left", + "align-right", + "amazon", + "ambulance", + "american-sign-language-interpreting", + "anchor", + "android", + "angellist", + "angle-double-down", + "angle-double-left", + "angle-double-right", + "angle-double-up", + "angle-down", + "angle-left", + "angle-right", + "angle-up", + "apple", + "archive", + "area-chart", + "arrow-circle-down", + "arrow-circle-left", + "arrow-circle-o-down", + "arrow-circle-o-left", + "arrow-circle-o-right", + "arrow-circle-o-up", + "arrow-circle-right", + "arrow-circle-up", + "arrow-down", + "arrow-left", + "arrow-right", + "arrow-up", + "arrows", + "arrows-alt", + "arrows-h", + "arrows-v", + "asl-interpreting", + "assistive-listening-systems", + "asterisk", + "at", + "audio-description", + "automobile", + "backward", + "balance-scale", + "ban", + "bandcamp", + "bank", + "bar-chart", + "bar-chart-o", + "barcode", + "bars", + "bath", + "bathtub", + "battery", + "battery-0", + "battery-1", + "battery-2", + "battery-3", + "battery-4", + "battery-empty", + "battery-full", + "battery-half", + "battery-quarter", + "battery-three-quarters", + "bed", + "beer", + "behance", + "behance-square", + "bell", + "bell-o", + "bell-slash", + "bell-slash-o", + "bicycle", + "binoculars", + "birthday-cake", + "bitbucket", + "bitbucket-square", + "bitcoin", + "black-tie", + "blind", + "bluetooth", + "bluetooth-b", + "bold", + "bolt", + "bomb", + "book", + "bookmark", + "bookmark-o", + "braille", + "briefcase", + "btc", + "bug", + "building", + "building-o", + "bullhorn", + "bullseye", + "bus", + "buysellads", + "cab", + "calculator", + "calendar", + "calendar-check-o", + "calendar-minus-o", + "calendar-o", + "calendar-plus-o", + "calendar-times-o", + "camera", + "camera-retro", + "car", + "caret-down", + "caret-left", + "caret-right", + "caret-square-o-down", + "caret-square-o-left", + "caret-square-o-right", + "caret-square-o-up", + "caret-up", + "cart-arrow-down", + "cart-plus", + "cc", + "cc-amex", + "cc-diners-club", + "cc-discover", + "cc-jcb", + "cc-mastercard", + "cc-paypal", + "cc-stripe", + "cc-visa", + "certificate", + "chain", + "chain-broken", + "check", + "check-circle", + "check-circle-o", + "check-square", + "check-square-o", + "chevron-circle-down", + "chevron-circle-left", + "chevron-circle-right", + "chevron-circle-up", + "chevron-down", + "chevron-left", + "chevron-right", + "chevron-up", + "child", + "chrome", + "circle", + "circle-o", + "circle-o-notch", + "circle-thin", + "clipboard", + "clock-o", + "clone", + "close", + "cloud", + "cloud-download", + "cloud-upload", + "cny", + "code", + "code-fork", + "codepen", + "codiepie", + "coffee", + "cog", + "cogs", + "columns", + "comment", + "comment-o", + "commenting", + "commenting-o", + "comments", + "comments-o", + "compass", + "compress", + "connectdevelop", + "contao", + "copy", + "copyright", + "creative-commons", + "credit-card", + "credit-card-alt", + "crop", + "crosshairs", + "css3", + "cube", + "cubes", + "cut", + "cutlery", + "dashboard", + "dashcube", + "database", + "deaf", + "deafness", + "dedent", + "delicious", + "desktop", + "deviantart", + "diamond", + "digg", + "dollar", + "dot-circle-o", + "download", + "dribbble", + "drivers-license", + "drivers-license-o", + "dropbox", + "drupal", + "edge", + "edit", + "eercast", + "eject", + "ellipsis-h", + "ellipsis-v", + "empire", + "envelope", + "envelope-o", + "envelope-open", + "envelope-open-o", + "envelope-square", + "envira", + "eraser", + "etsy", + "eur", + "euro", + "exchange", + "exclamation", + "exclamation-circle", + "exclamation-triangle", + "expand", + "expeditedssl", + "external-link", + "external-link-square", + "eye", + "eye-slash", + "eyedropper", + "fa", + "facebook", + "facebook-f", + "facebook-official", + "facebook-square", + "fast-backward", + "fast-forward", + "fax", + "feed", + "female", + "fighter-jet", + "file", + "file-archive-o", + "file-audio-o", + "file-code-o", + "file-excel-o", + "file-image-o", + "file-movie-o", + "file-o", + "file-pdf-o", + "file-photo-o", + "file-picture-o", + "file-powerpoint-o", + "file-sound-o", + "file-text", + "file-text-o", + "file-video-o", + "file-word-o", + "file-zip-o", + "files-o", + "film", + "filter", + "fire", + "fire-extinguisher", + "firefox", + "first-order", + "flag", + "flag-checkered", + "flag-o", + "flash", + "flask", + "flickr", + "floppy-o", + "folder", + "folder-o", + "folder-open", + "folder-open-o", + "font", + "font-awesome", + "fonticons", + "fort-awesome", + "forumbee", + "forward", + "foursquare", + "free-code-camp", + "frown-o", + "futbol-o", + "gamepad", + "gavel", + "gbp", + "ge", + "gear", + "gears", + "genderless", + "get-pocket", + "gg", + "gg-circle", + "gift", + "git", + "git-square", + "github", + "github-alt", + "github-square", + "gitlab", + "gittip", + "glass", + "glide", + "glide-g", + "globe", + "google", + "google-plus", + "google-plus-circle", + "google-plus-official", + "google-plus-square", + "google-wallet", + "graduation-cap", + "gratipay", + "grav", + "group", + "h-square", + "hacker-news", + "hand-grab-o", + "hand-lizard-o", + "hand-o-down", + "hand-o-left", + "hand-o-right", + "hand-o-up", + "hand-paper-o", + "hand-peace-o", + "hand-pointer-o", + "hand-rock-o", + "hand-scissors-o", + "hand-spock-o", + "hand-stop-o", + "handshake-o", + "hard-of-hearing", + "hashtag", + "hdd-o", + "header", + "headphones", + "heart", + "heart-o", + "heartbeat", + "history", + "home", + "hospital-o", + "hotel", + "hourglass", + "hourglass-1", + "hourglass-2", + "hourglass-3", + "hourglass-end", + "hourglass-half", + "hourglass-o", + "hourglass-start", + "houzz", + "html5", + "i-cursor", + "id-badge", + "id-card", + "id-card-o", + "ils", + "image", + "imdb", + "inbox", + "indent", + "industry", + "info", + "info-circle", + "inr", + "instagram", + "institution", + "internet-explorer", + "intersex", + "ioxhost", + "italic", + "joomla", + "jpy", + "jsfiddle", + "key", + "keyboard-o", + "krw", + "language", + "laptop", + "lastfm", + "lastfm-square", + "leaf", + "leanpub", + "legal", + "lemon-o", + "level-down", + "level-up", + "life-bouy", + "life-buoy", + "life-ring", + "life-saver", + "lightbulb-o", + "line-chart", + "link", + "linkedin", + "linkedin-square", + "linode", + "linux", + "list", + "list-alt", + "list-ol", + "list-ul", + "location-arrow", + "lock", + "long-arrow-down", + "long-arrow-left", + "long-arrow-right", + "long-arrow-up", + "low-vision", + "magic", + "magnet", + "mail-forward", + "mail-reply", + "mail-reply-all", + "male", + "map", + "map-marker", + "map-o", + "map-pin", + "map-signs", + "mars", + "mars-double", + "mars-stroke", + "mars-stroke-h", + "mars-stroke-v", + "maxcdn", + "meanpath", + "medium", + "medkit", + "meetup", + "meh-o", + "mercury", + "microchip", + "microphone", + "microphone-slash", + "minus", + "minus-circle", + "minus-square", + "minus-square-o", + "mixcloud", + "mobile", + "mobile-phone", + "modx", + "money", + "moon-o", + "mortar-board", + "motorcycle", + "mouse-pointer", + "music", + "navicon", + "neuter", + "newspaper-o", + "object-group", + "object-ungroup", + "odnoklassniki", + "odnoklassniki-square", + "opencart", + "openid", + "opera", + "optin-monster", + "outdent", + "pagelines", + "paint-brush", + "paper-plane", + "paper-plane-o", + "paperclip", + "paragraph", + "paste", + "pause", + "pause-circle", + "pause-circle-o", + "paw", + "paypal", + "pencil", + "pencil-square", + "pencil-square-o", + "percent", + "phone", + "phone-square", + "photo", + "picture-o", + "pie-chart", + "pied-piper", + "pied-piper-alt", + "pied-piper-pp", + "pinterest", + "pinterest-p", + "pinterest-square", + "plane", + "play", + "play-circle", + "play-circle-o", + "plug", + "plus", + "plus-circle", + "plus-square", + "plus-square-o", + "podcast", + "power-off", + "print", + "product-hunt", + "puzzle-piece", + "qq", + "qrcode", + "question", + "question-circle", + "question-circle-o", + "quora", + "quote-left", + "quote-right", + "ra", + "random", + "ravelry", + "rebel", + "recycle", + "reddit", + "reddit-alien", + "reddit-square", + "refresh", + "registered", + "remove", + "renren", + "reorder", + "repeat", + "reply", + "reply-all", + "resistance", + "retweet", + "rmb", + "road", + "rocket", + "rotate-left", + "rotate-right", + "rouble", + "rss", + "rss-square", + "rub", + "ruble", + "rupee", + "s15", + "safari", + "save", + "scissors", + "scribd", + "search", + "search-minus", + "search-plus", + "sellsy", + "send", + "send-o", + "server", + "share", + "share-alt", + "share-alt-square", + "share-square", + "share-square-o", + "shekel", + "sheqel", + "shield", + "ship", + "shirtsinbulk", + "shopping-bag", + "shopping-basket", + "shopping-cart", + "shower", + "sign-in", + "sign-language", + "sign-out", + "signal", + "signing", + "simplybuilt", + "sitemap", + "skyatlas", + "skype", + "slack", + "sliders", + "slideshare", + "smile-o", + "snapchat", + "snapchat-ghost", + "snapchat-square", + "snowflake-o", + "soccer-ball-o", + "sort", + "sort-alpha-asc", + "sort-alpha-desc", + "sort-amount-asc", + "sort-amount-desc", + "sort-asc", + "sort-desc", + "sort-down", + "sort-numeric-asc", + "sort-numeric-desc", + "sort-up", + "soundcloud", + "space-shuttle", + "spinner", + "spoon", + "spotify", + "square", + "square-o", + "stack-exchange", + "stack-overflow", + "star", + "star-half", + "star-half-empty", + "star-half-full", + "star-half-o", + "star-o", + "steam", + "steam-square", + "step-backward", + "step-forward", + "stethoscope", + "sticky-note", + "sticky-note-o", + "stop", + "stop-circle", + "stop-circle-o", + "street-view", + "strikethrough", + "stumbleupon", + "stumbleupon-circle", + "subscript", + "subway", + "suitcase", + "sun-o", + "superpowers", + "superscript", + "support", + "table", + "tablet", + "tachometer", + "tag", + "tags", + "tasks", + "taxi", + "telegram", + "television", + "tencent-weibo", + "terminal", + "text-height", + "text-width", + "th", + "th-large", + "th-list", + "themeisle", + "thermometer", + "thermometer-0", + "thermometer-1", + "thermometer-2", + "thermometer-3", + "thermometer-4", + "thermometer-empty", + "thermometer-full", + "thermometer-half", + "thermometer-quarter", + "thermometer-three-quarters", + "thumb-tack", + "thumbs-down", + "thumbs-o-down", + "thumbs-o-up", + "thumbs-up", + "ticket", + "times", + "times-circle", + "times-circle-o", + "times-rectangle", + "times-rectangle-o", + "tint", + "toggle-down", + "toggle-left", + "toggle-off", + "toggle-on", + "toggle-right", + "toggle-up", + "trademark", + "train", + "transgender", + "transgender-alt", + "trash", + "trash-o", + "tree", + "trello", + "tripadvisor", + "trophy", + "truck", + "try", + "tty", + "tumblr", + "tumblr-square", + "turkish-lira", + "tv", + "twitch", + "twitter", + "twitter-square", + "umbrella", + "underline", + "undo", + "universal-access", + "university", + "unlink", + "unlock", + "unlock-alt", + "unsorted", + "upload", + "usb", + "usd", + "user", + "user-circle", + "user-circle-o", + "user-md", + "user-o", + "user-plus", + "user-secret", + "user-times", + "users", + "vcard", + "vcard-o", + "venus", + "venus-double", + "venus-mars", + "viacoin", + "viadeo", + "viadeo-square", + "video-camera", + "vimeo", + "vimeo-square", + "vine", + "vk", + "volume-control-phone", + "volume-down", + "volume-off", + "volume-up", + "warning", + "wechat", + "weibo", + "weixin", + "whatsapp", + "wheelchair", + "wheelchair-alt", + "wifi", + "wikipedia-w", + "window-close", + "window-close-o", + "window-maximize", + "window-minimize", + "window-restore", + "windows", + "won", + "wordpress", + "wpbeginner", + "wpexplorer", + "wpforms", + "wrench", + "xing", + "xing-square", + "y-combinator", + "y-combinator-square", + "yahoo", + "yc", + "yc-square", + "yelp", + "yen", + "yoast", + "youtube", + "youtube-play", + "youtube-square" +] diff --git a/src/lib/form.ts b/src/lib/form.ts new file mode 100644 index 0000000..a1f8cfd --- /dev/null +++ b/src/lib/form.ts @@ -0,0 +1,80 @@ +import { h } from 'lib/h' +import * as Layout from 'lib/layout' +import * as Button from 'lib/button' + +export function input(id: string, label: string, attrs: object): Element { + return h('div', + { className: 'g-Form__Field' }, + h('div', + { className: 'g-Form__Label' }, + h('label', { for: id }, label) + ), + h('input', { id: id, ...attrs }) + ) +} + +export function colorInput( + defaultColors: string[], + id: string, + label: string, + initValue: string, + onInput: (value: string) => void +): Element { + const input = h('input', + { id, + value: initValue, + type: 'color', + oninput: (e: Event) => { + if (e.target !== null) { + onInput((e.target as HTMLInputElement).value) + } + } + } + ) as HTMLInputElement + return h('div', + { className: 'g-Form__Field' }, + h('div', + { className: 'g-Form__Label' }, + h('label', { for: id }, label) + ), + Layout.line( + {}, + ...(defaultColors.map(color => + Button.raw({ className: 'g-Form__DefaultColor', + style: `background-color: ${color}`, + type: 'button', + onclick: () => { + input.value = color + onInput(color) + } + }) + ).concat(input)) + ) + ) +} + +export function textarea( + id: string, + label: string, + initValue: string, + onInput: (value: string) => void +): Element { + return h('div', + { className: 'g-Form__Field' }, + h('div', + { className: 'g-Form__Label' }, + h('label', { for: id }, label) + ), + h('textarea', + { id, + className: 'g-Form__Textarea', + oninput: (e: Event) => { + if (e.target !== null) { + onInput((e.target as HTMLTextAreaElement).value) + } + } + }, + initValue + ) + ) +} diff --git a/src/lib/h.ts b/src/lib/h.ts new file mode 100644 index 0000000..1e49f2f --- /dev/null +++ b/src/lib/h.ts @@ -0,0 +1,41 @@ +type Child = Element | Text | string | number + +export type Children = Child[] + +export function h( + tagName: string, + attrs: object, + ...children: Children +): Element { + const isSvg = tagName === 'svg' || tagName === 'path' + + let elem = isSvg + ? document.createElementNS('http://www.w3.org/2000/svg', tagName) + : document.createElement(tagName) + + if (isSvg) { + Object.entries(attrs).forEach(([key, value]) => { + elem.setAttribute(key, value) + }) + } else { + elem = Object.assign(elem, attrs) + } + + for (const child of children) { + if (typeof child === 'number') + elem.append(child.toString()) + else + elem.append(child) + // if (Array.isArray(child)) + // elem.append(...child) + // else + // elem.append(child) + } + + return elem +} + +export function concatClassName(attrs: any, className: string): object { + const existingClassName = 'className' in attrs ? attrs['className'] : undefined + return { ...attrs, className: `${className} ${existingClassName}` } +} diff --git a/src/lib/layout.ts b/src/lib/layout.ts new file mode 100644 index 0000000..1e38bfd --- /dev/null +++ b/src/lib/layout.ts @@ -0,0 +1,15 @@ +import { h, Children, concatClassName } from 'lib/h' + +export function section(attrs: object, ...children: Children): Element { + return h('div', + concatClassName(attrs, 'g-Layout__Section'), + ...children + ) +} + +export function line(attrs: object, ...children: Children): Element { + return h('div', + concatClassName(attrs, 'g-Layout__Line'), + ...children + ) +} diff --git a/src/lib/modal.ts b/src/lib/modal.ts new file mode 100644 index 0000000..4f8c675 --- /dev/null +++ b/src/lib/modal.ts @@ -0,0 +1,28 @@ +import { h } from 'lib/h' +import * as Button from 'lib/button' + +export function show(content: Element) { + document.body.appendChild(h('div', + { id: 'g-Modal' }, + h('div', + { className: 'g-Modal__Curtain', + onclick: () => hide() + } + ), + h('div', + { className: 'g-Modal__Window' }, + Button.raw( + { className: 'g-Modal__Close', + onclick: () => hide() + }, + h('div', { className: 'fa fa-close' }) + ), + content + ) + )) +} + +export function hide() { + const modal = document.querySelector('#g-Modal') + modal && document.body.removeChild(modal) +} |