-
+
+
-
-
+
+
+ {!imageFile || showLoadingState ? 'Working…' :
+
+ }
+
+
+
+ {(downloadUrl && imageFile) && (
+
+
+
+ )}
+ {showLoadingState &&
}
+
- {(downloadUrl && imageFile) && (
-
-
-
- )}
+
);
}
diff --git a/src/components/Options/style.scss b/src/components/Options/style.scss
index 4948a06d..a8c3a64f 100644
--- a/src/components/Options/style.scss
+++ b/src/components/Options/style.scss
@@ -1,225 +1,166 @@
-/*
-Note: These styles are temporary. They will be replaced before going live.
-*/
-
-.row {
- padding: 5px;
- margin: 0 10px;
-}
+$horizontalPadding: 15px;
.options {
- box-sizing: border-box;
- padding: 0;
- background: rgba(40,40,40,0.8);
- box-shadow: 0 1px 3px rgba(0,0,0,0.5);
- color: #eee;
- overflow: auto;
- z-index: 1;
+ color: #fff;
+ width: 300px;
opacity: 0.9;
- transform-origin: 50% 140%;
- transition: opacity 300ms linear;
- animation: options-open 500ms cubic-bezier(.6,1.6,.6,1) forwards 1;
-
- &.horizontal {
- border-radius: 1px 1px 5px 5px;
- width: 230px;
-
- > .inner {
- max-height: 80vh;
- overflow: auto;
- -webkit-overflow-scrolling: touch;
- -ms-touch-action: pan-y;
- touch-action: pan-y;
- }
- }
-
- &.vertical {
- opacity: 1;
- margin: 0 5px 10px;
- border-radius: 0 0 5px 5px;
- }
-
- &:hover, &:focus, &:focus-within {
- opacity: 1;
- }
-
- @keyframes options-open {
- from {
- transform: translateY(100px) scale(.8);
- }
- }
-
- .content {
- max-height: calc(75vh - 100px);
- overflow: auto;
- touch-action: pan-y;
- -webkit-overflow-scrolling: touch;
- }
-
- .picker {
- margin: 5px 15px;
-
- select {
- display: block;
- width: 100%;
- box-sizing: border-box;
- -webkit-appearance: none;
- appearance: none;
- padding: 10px 30px 10px 10px;
- background: url('data:image/svg+xml,
') right center no-repeat;
- background-color: var(--gray-dark);
- opacity: 0.9;
- border: none;
- font: inherit;
- color: white;
- transition: box-shadow 150ms ease;
-
- &:hover {
- opacity: 1;
- }
- &:focus {
- opacity: 1;
- outline: none;
- box-shadow: 0 0 0 2px var(--button-fg, #ccc);
- }
- }
- }
-
- .title {
- display: flex;
- align-items: center;
- padding: 10px 15px;
- margin: 0 0 12px;
- background: rgba(0,0,0,0.9);
- font: inherit;
- }
-
- label {
- display: block;
- padding: 5px;
- margin: 0 10px;
- display: flex;
- flex-wrap: wrap;
-
- // prevent labels from wrapping below checkboxes
- > span {
- flex: 1;
- }
-
- input[type=checkbox],
- input[type=radio] {
- flex: 0;
- margin: 2px 8px 0 0;
- }
-
- range-input {
- display: block;
- flex: 1 0 100%;
- margin: 2px 0;
- }
- }
-
- hr {
- height: 1px;
- border: none;
- margin: 5px 0;
- box-shadow: inset 0 0.5px 0 rgba(0, 0, 0, 0.4), inset 0 -0.5px 0 rgba(255, 255, 255, 0.2);
- }
+ font-size: 1.2rem;
+ max-height: 100%;
+ display: flex;
+ flex-flow: column;
}
-.picker {
- margin: 5px 15px;
-
- select {
- display: block;
- width: 100%;
- box-sizing: border-box;
- -webkit-appearance: none;
- appearance: none;
- padding: 10px 30px 10px 10px;
- background: url('data:image/svg+xml,
') right center no-repeat;
- background-color: var(--gray-dark);
- opacity: 0.9;
- border: none;
- font: inherit;
- color: white;
- }
-
- hr {
- height: 1px;
- border: none;
- margin: 5px 0;
- box-shadow: inset 0 0.5px 0 rgba(0, 0, 0, 0.4), inset 0 -0.5px 0 rgba(255, 255, 255, 0.2);
- }
+.options-title {
+ background: rgba(0, 0, 0, 0.9);
+ margin: 0;
+ padding: 10px $horizontalPadding;
+ font-weight: normal;
+ font-size: 1.4rem;
+ border-bottom: 1px solid #000;
}
-.size-details {
+.option-text-first {
+ display: grid;
+ align-items: center;
+ grid-template-columns: 87px 1fr;
+ grid-gap: 0.7em;
+ padding: 10px $horizontalPadding;
+}
+
+.option-one-cell {
+ display: grid;
+ grid-template-columns: 1fr;
+ padding: 10px $horizontalPadding;
+}
+
+.option-input-first,
+.section-enabler {
+ cursor: pointer;
+ display: grid;
+ align-items: center;
+ grid-template-columns: auto 1fr;
+ grid-gap: 0.7em;
+ padding: 10px $horizontalPadding;
+}
+
+.section-enabler {
+ background: rgba(0, 0, 0, 0.8);
+}
+
+.options-section {
+ background: rgba(0, 0, 0, 0.7);
+}
+
+.text-field {
+ background: #fff;
+ font: inherit;
+ border: none;
+ padding: 2px 0 2px 10px;
+ width: 100%;
+ box-sizing: border-box;
+ box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.5);
+}
+
+.options-scroller {
+ overflow-x: hidden;
+ overflow-y: auto;
+}
+
+.results {
+ display: grid;
+ grid-template-columns: 1fr auto;
+ background: rgba(0, 0, 0, 0.9);
+ font-size: 1.4rem;
+}
+
+.result-data {
display: flex;
align-items: center;
- padding: 5px 15px;
- background: rgba(0,0,0,0.5);
+ padding: 0 $horizontalPadding;
+}
+
+.size-delta {
+ font-size: 1.1rem;
+ font-style: italic;
+ position: relative;
+ top: -1px;
+ margin-left: 0.3em;
+}
+
+.size-increase {
+ color: #e35050;
+}
+
+.size-decrease {
+ color: #50e3c2;
+}
+
+.options-copy {
+ display: grid;
+ background: rgba(0, 0, 0, 0.9);
+ padding: 5px;
+}
+
+.copy-button {
+ composes: unbutton from '../../lib/util.scss';
+ background: #484848;
+ border-radius: 4px;
+ color: #fff;
+ text-align: left;
+ padding: 5px 10px;
+}
+
+@keyframes action-enter {
+ from {
+ transform: rotate(-90deg);
+ opacity: 0;
+ animation-timing-function: ease-out;
+ }
+}
+
+@keyframes action-leave {
+ from {
+ transform: rotate(0deg);
+ opacity: 1;
+ animation-timing-function: ease-out;
+ }
}
.download {
- flex: 0;
- margin: 0 0 0 auto;
- background: rgba(255,255,255,0.1);
- border-radius: 50%;
- padding: 5px;
- width: 16px;
- height: 16px;
- text-decoration: none;
-
- > svg {
- width: 16px;
- height: 16px;
- fill: #fff;
- }
-
- &:hover {
- background-color: rgba(255,255,255,0.3);
- }
+ background: #34B9EB;
+ --size: 38px;
+ width: var(--size);
+ height: var(--size);
+ display: grid;
+ align-items: center;
+ justify-items: center;
}
-.size-details {
- padding: 5px 15px;
- background: rgba(0,0,0,0.5);
+.download-link {
+ animation: action-enter 0.2s;
+ grid-area: 1/1;
}
-.size {
- font-weight: normal;
+.download-link-disable {
+ pointer-events: none;
+ opacity: 0;
+ transform: rotate(90deg);
+ animation: action-leave 0.2s;
}
-.increase,
-.decrease {
- font-style: italic;
- filter: #{"grayscale(calc(50% - var(--size-delta, 50) * 0.5%))"};
-
- &:before {
- content: ' (';
- }
- &:after {
- content: ')';
- }
+.download-icon {
+ color: #fff;
+ display: block;
+ --size: 24px;
+ width: var(--size);
+ height: var(--size);
+ padding: 7px;
+ filter: drop-shadow(0 1px 0 rgba(0, 0, 0, 0.7));
}
-.increase {
- color: var(--negative);
-}
-.decrease {
- color: var(--positive);
-}
-
-.preprocessors {
- padding: 5px 0;
- margin: 5px 0;
- box-shadow: inset 0 -.5px 0 rgba(0,0,0,0.4), 0 .5px 0 rgba(255,255,255,0.2);
-}
-
-.toggle {
- display: flex;
- position: relative;
- align-content: center;
- font-size: 14px;
- box-shadow: inset 0 -.5px 0 rgba(0,0,0,0.4), 0 .5px 0 rgba(255,255,255,0.2);
+.spinner {
+ --color: #fff;
+ --delay: 0;
+ --size: 22px;
+ grid-area: 1/1;
}
diff --git a/src/components/Output/custom-els/TwoUp/styles.css b/src/components/Output/custom-els/TwoUp/styles.css
index 8111754f..d40b8735 100644
--- a/src/components/Output/custom-els/TwoUp/styles.css
+++ b/src/components/Output/custom-els/TwoUp/styles.css
@@ -52,7 +52,7 @@ two-up[legacy-clip-compat] > :not(.two-up-handle) {
height: calc(var(--thumb-size) * 0.9);
background: var(--thumb-background);
border: 1px solid rgba(0,0,0,0.2);
- border-radius: calc(var(--thumb-size) * 0.08);
+ border-radius: var(--thumb-size);
box-shadow: 0 1px 4px rgba(0,0,0,0.1);
color: var(--thumb-color);
box-sizing: border-box;
diff --git a/src/components/Output/style.scss b/src/components/Output/style.scss
index dd74b11b..62bf79bf 100644
--- a/src/components/Output/style.scss
+++ b/src/components/Output/style.scss
@@ -47,10 +47,10 @@ Note: These styles are temporary. They will be replaced before going live.
flex-wrap: wrap;
contain: content;
- @media (min-width: 680px) {
+ @media (min-width: 860px) {
top: auto;
- left: 220px;
- right: 220px;
+ left: 320px;
+ right: 320px;
bottom: 0;
}
}
diff --git a/src/components/checkbox/index.tsx b/src/components/checkbox/index.tsx
new file mode 100644
index 00000000..9ed202ab
--- /dev/null
+++ b/src/components/checkbox/index.tsx
@@ -0,0 +1,20 @@
+import { h, Component } from 'preact';
+import * as style from './style.scss';
+import { UncheckedIcon, CheckedIcon } from '../../lib/icons';
+
+interface Props extends JSX.HTMLAttributes {}
+interface State {}
+
+export default class Checkbox extends Component
{
+ render(props: Props) {
+ return (
+
+ {props.checked
+ ?
+ :
+ }
+
+
+ );
+ }
+}
diff --git a/src/components/checkbox/style.scss b/src/components/checkbox/style.scss
new file mode 100644
index 00000000..67aecd6a
--- /dev/null
+++ b/src/components/checkbox/style.scss
@@ -0,0 +1,22 @@
+.checkbox {
+ display: inline-block;
+ position: relative;
+ --size: 17px;
+}
+
+.real-checkbox {
+ top: 0;
+ position: absolute;
+ opacity: 0;
+ pointer-events: none;
+}
+
+.icon {
+ display: block;
+ width: var(--size);
+ height: var(--size);
+}
+
+.checked {
+ fill: #34B9EB;
+}
diff --git a/src/components/compress/index.tsx b/src/components/compress/index.tsx
index 0568ff41..700c244e 100644
--- a/src/components/compress/index.tsx
+++ b/src/components/compress/index.tsx
@@ -65,7 +65,9 @@ interface Props {
interface State {
source?: SourceImage;
images: [EncodedImage, EncodedImage];
+ /** Source image load */
loading: boolean;
+ loadingCounter: number;
error?: string;
orientation: Orientation;
}
@@ -159,6 +161,7 @@ export default class Compress extends Component {
state: State = {
source: undefined,
loading: false,
+ loadingCounter: 0,
images: [
{
preprocessorState: defaultPreprocessorState,
@@ -252,7 +255,9 @@ export default class Compress extends Component {
@bind
private async updateFile(file: File | Fileish) {
- this.setState({ loading: true });
+ const loadingCounter = this.state.loadingCounter + 1;
+
+ this.setState({ loadingCounter, loading: true });
// Abort any current encode jobs, as they're redundant now.
this.leftProcessor.abortCurrent();
@@ -273,6 +278,9 @@ export default class Compress extends Component {
data = await decodeImage(file, this.leftProcessor);
}
+ // Another file has been opened before this one processed.
+ if (this.state.loadingCounter !== loadingCounter) return;
+
let newState: State = {
...this.state,
source: { data, file, vectorImage },
@@ -303,6 +311,8 @@ export default class Compress extends Component {
} catch (err) {
if (err.name === 'AbortError') return;
console.error(err);
+ // Another file has been opened before this one processed.
+ if (this.state.loadingCounter !== loadingCounter) return;
this.props.onError('Invalid image');
this.setState({ loading: false });
}
@@ -388,10 +398,9 @@ export default class Compress extends Component {
render({ }: Props, { loading, images, source, orientation }: State) {
const [leftImage, rightImage] = images;
const [leftImageData, rightImageData] = images.map(i => i.data);
- const anyLoading = loading || images.some(image => image.loading);
return (
-
+
);
}
diff --git a/src/components/compress/style.scss b/src/components/compress/style.scss
index 29fefe9f..efb7e628 100644
--- a/src/components/compress/style.scss
+++ b/src/components/compress/style.scss
@@ -1,19 +1,22 @@
.compress {
- height: 100%;
-}
-
-.option-pair {
- display: flex;
width: 100%;
height: 100%;
+ contain: strict;
+ display: grid;
&.horizontal {
- justify-content: space-between;
- align-items: flex-end;
+ grid-template-columns: 1fr auto;
+ grid-template-rows: calc(100% - 75px);
+
+ @media (min-width: 860px) {
+ grid-template-rows: 100%;
+ }
+ align-items: end;
+ align-content: end;
}
&.vertical {
- flex-direction: column;
- justify-content: flex-end;
+ // TODO: make the mobile view work
+ background: red;
}
}
diff --git a/src/components/expander/index.tsx b/src/components/expander/index.tsx
new file mode 100644
index 00000000..49099cc2
--- /dev/null
+++ b/src/components/expander/index.tsx
@@ -0,0 +1,92 @@
+import { h, Component, ComponentChild, ComponentChildren } from 'preact';
+import * as style from './style.scss';
+import { linkRef } from '../../lib/initial-util';
+
+interface Props {
+ children: ComponentChildren;
+}
+interface State {
+ outgoingChildren: ComponentChild[];
+}
+
+export default class Expander extends Component
{
+ state: State = {
+ outgoingChildren: [],
+ };
+ private el?: HTMLDivElement;
+ private lastElHeight: number = 0;
+
+ componentWillReceiveProps(nextProps: Props) {
+ const children = this.props.children as ComponentChild[];
+ const nextChildren = nextProps.children as ComponentChild[];
+
+ if (!nextChildren[0] && children[0]) {
+ // Cache the current children for the shrink animation.
+ this.setState({ outgoingChildren: children });
+ }
+ }
+
+ componentWillUpdate(nextProps: Props) {
+ const children = this.props.children as ComponentChild[];
+ const nextChildren = nextProps.children as ComponentChild[];
+
+ // Only interested if going from empty to not-empty, or not-empty to empty.
+ if ((children[0] && nextChildren[0]) || (!children[0] && !nextChildren[0])) return;
+ this.lastElHeight = this.el!.getBoundingClientRect().height;
+ }
+
+ componentDidUpdate(previousProps: Props) {
+ const children = this.props.children as ComponentChild[];
+ const previousChildren = previousProps.children as ComponentChild[];
+
+ // Only interested if going from empty to not-empty, or not-empty to empty.
+ if ((children[0] && previousChildren[0]) || (!children[0] && !previousChildren[0])) return;
+
+ // What height do we need to transition to?
+ this.el!.style.transition = 'none';
+ this.el!.style.height = '';
+ const newHeight = children[0] ? this.el!.getBoundingClientRect().height : 0;
+
+ if (this.lastElHeight === newHeight) {
+ this.el!.style.transition = '';
+ return;
+ }
+
+ // Set the currently rendered height absolutely.
+ this.el!.style.height = this.lastElHeight + 'px';
+ this.el!.style.transition = '';
+ this.el!.style.overflow = 'hidden';
+ // Force a style calc so the browser picks up the start value.
+ getComputedStyle(this.el!).height;
+ // Animate to the new height.
+ this.el!.style.height = newHeight + 'px';
+
+ const listener = () => {
+ // Unset the height & overflow, so element changes do the right thing.
+ this.el!.style.height = '';
+ this.el!.style.overflow = '';
+ this.el!.removeEventListener('transitionend', listener);
+ this.el!.removeEventListener('transitioncancel', listener);
+ if (this.state.outgoingChildren[0]) {
+ this.setState({ outgoingChildren: [] });
+ }
+ };
+
+ this.el!.addEventListener('transitionend', listener);
+ this.el!.addEventListener('transitioncancel', listener);
+ }
+
+ render(props: Props, { outgoingChildren }: State) {
+ const children = props.children as ComponentChild[];
+ const childrenExiting = !children[0] && outgoingChildren[0];
+
+ return (
+
+ {children[0] ? children : outgoingChildren}
+
+ );
+ }
+}
diff --git a/src/components/expander/style.scss b/src/components/expander/style.scss
new file mode 100644
index 00000000..f72985b7
--- /dev/null
+++ b/src/components/expander/style.scss
@@ -0,0 +1,9 @@
+.expander {
+ transition: height 200ms ease-in-out;
+}
+
+.children-exiting {
+ & > * {
+ pointer-events: none;
+ }
+}
diff --git a/src/components/range/index.tsx b/src/components/range/index.tsx
new file mode 100644
index 00000000..fabc75c9
--- /dev/null
+++ b/src/components/range/index.tsx
@@ -0,0 +1,55 @@
+import { h, Component } from 'preact';
+import * as style from './style.scss';
+import RangeInputElement from '../../custom-els/RangeInput';
+import '../../custom-els/RangeInput';
+import { linkRef, bind } from '../../lib/initial-util';
+
+interface Props extends JSX.HTMLAttributes {}
+interface State {}
+
+export default class Range extends Component {
+ rangeWc?: RangeInputElement;
+
+ @bind
+ private onTextInput(event: Event) {
+ const input = event.target as HTMLInputElement;
+ this.rangeWc!.value = input.value;
+ const { onInput } = this.props;
+ if (onInput) onInput(event);
+ }
+
+ render(props: Props) {
+ const {
+ children,
+ ...otherProps
+ } = props;
+
+ const {
+ value, min, max, step,
+ } = props;
+
+ return (
+
+ );
+ }
+}
diff --git a/src/components/range/style.scss b/src/components/range/style.scss
new file mode 100644
index 00000000..ffe0d54e
--- /dev/null
+++ b/src/components/range/style.scss
@@ -0,0 +1,55 @@
+.range {
+ position: relative;
+ z-index: 0;
+ display: grid;
+ grid-template-columns: 1fr auto;
+}
+
+.label-text {
+ color: #fff; /* TEMP */
+}
+
+.range-wc-container {
+ position: relative;
+ z-index: 1;
+ grid-row: 2 / 3;
+ grid-column: 1 / 3;
+}
+
+.range-wc {
+ width: 100%;
+}
+
+.text-input {
+ grid-row: 1 / 2;
+ grid-column: 2 / 3;
+
+ text-align: right;
+ background: transparent;
+ color: inherit;
+ font: inherit;
+ border: none;
+ padding: 2px 5px;
+ box-sizing: border-box;
+ text-decoration: underline;
+ text-decoration-style: dotted;
+ text-underline-position: under;
+ width: 48px;
+ position: relative;
+ left: 5px;
+
+ &:focus {
+ background: #fff;
+ color: #000;
+ }
+
+ // Remove the number controls
+ -moz-appearance: textfield;
+
+ &::-webkit-outer-spin-button,
+ &::-webkit-inner-spin-button {
+ -moz-appearance: none;
+ -webkit-appearance: none;
+ margin: 0;
+ }
+}
diff --git a/src/components/select/index.tsx b/src/components/select/index.tsx
new file mode 100644
index 00000000..cbaf4541
--- /dev/null
+++ b/src/components/select/index.tsx
@@ -0,0 +1,20 @@
+import { h, Component } from 'preact';
+import * as style from './style.scss';
+
+interface Props extends JSX.HTMLAttributes {
+ large?: boolean;
+}
+interface State {}
+
+export default class Select extends Component {
+ render(props: Props) {
+ const { large, ...otherProps } = props;
+
+ return (
+
+ );
+ }
+}
diff --git a/src/components/select/style.scss b/src/components/select/style.scss
new file mode 100644
index 00000000..b13ea914
--- /dev/null
+++ b/src/components/select/style.scss
@@ -0,0 +1,33 @@
+.select {
+ position: relative;
+}
+
+.native-select {
+ background: #2f2f2f;
+ border-radius: 4px;
+ font: inherit;
+ padding: 4px 25px 4px 10px;
+ -webkit-appearance: none;
+ -moz-appearance: none;
+ border: none;
+ color: #fff;
+ width: 100%;
+}
+
+.arrow {
+ position: absolute;
+ right: 8px;
+ top: 50%;
+ transform: translateY(-50%);
+ fill: #fff;
+ width: 10px;
+}
+
+.large {
+ padding: 10px 35px 10px 10px;
+ background: #151515;
+
+ & .arrow {
+ right: 13px;
+ }
+}
diff --git a/src/custom-els/RangeInput/index.ts b/src/custom-els/RangeInput/index.ts
index 58ed63b5..1195fa79 100644
--- a/src/custom-els/RangeInput/index.ts
+++ b/src/custom-els/RangeInput/index.ts
@@ -1,15 +1,19 @@
import { bind } from '../../lib/initial-util';
-import './styles.css';
+import * as style from './styles.css';
const RETARGETED_EVENTS = ['focus', 'blur'];
const UPDATE_EVENTS = ['input', 'change'];
const REFLECTED_PROPERTIES = ['name', 'min', 'max', 'step', 'value', 'disabled'];
const REFLECTED_ATTRIBUTES = ['name', 'min', 'max', 'step', 'value', 'disabled'];
+function getPrescision(value: string): number {
+ const afterDecimal = value.split('.')[1];
+ return afterDecimal ? afterDecimal.length : 0;
+}
+
class RangeInputElement extends HTMLElement {
- private _input = document.createElement('input');
- private _valueDisplayWrapper = document.createElement('div');
- private _valueDisplay = document.createElement('span');
+ private _input: HTMLInputElement;
+ private _valueDisplay?: HTMLDivElement;
private _ignoreChange = false;
static get observedAttributes() {
@@ -18,7 +22,9 @@ class RangeInputElement extends HTMLElement {
constructor() {
super();
+ this._input = document.createElement('input');
this._input.type = 'range';
+ this._input.className = style.input;
for (const event of RETARGETED_EVENTS) {
this._input.addEventListener(event, this._retargetEvent, true);
@@ -29,6 +35,18 @@ class RangeInputElement extends HTMLElement {
}
}
+ connectedCallback() {
+ if (this.contains(this._input)) return;
+ this.innerHTML =
+ `';
+
+ this.insertBefore(this._input, this.firstChild);
+ this._valueDisplay = this.querySelector('.' + style.valueDisplay) as HTMLDivElement;
+ }
+
get labelPrecision(): string {
return this.getAttribute('label-precision') || '';
}
@@ -37,14 +55,6 @@ class RangeInputElement extends HTMLElement {
this.setAttribute('label-precision', precision);
}
- connectedCallback() {
- if (this._input.parentNode !== this) {
- this.appendChild(this._input);
- this._valueDisplayWrapper.appendChild(this._valueDisplay);
- this.appendChild(this._valueDisplayWrapper);
- }
- }
-
attributeChangedCallback(name: string, oldValue: string, newValue: string | null) {
if (this._ignoreChange) return;
if (newValue === null) {
@@ -65,15 +75,15 @@ class RangeInputElement extends HTMLElement {
@bind
private _update() {
- const value = parseFloat(this.value || '0');
- const min = parseFloat(this.min || '0');
- const max = parseFloat(this.max || '100');
- const labelPrecision = parseFloat(this.labelPrecision || '0');
+ const value = Number(this.value) || 0;
+ const min = Number(this.min) || 0;
+ const max = Number(this.max) || 100;
+ const labelPrecision = Number(this.labelPrecision) || getPrescision(this.step) || 0;
const percent = 100 * (value - min) / (max - min);
- const displayValue = labelPrecision ? value.toPrecision(labelPrecision) :
+ const displayValue = labelPrecision ? value.toFixed(labelPrecision) :
Math.round(value).toString();
- this._valueDisplay.textContent = displayValue;
+ this._valueDisplay!.textContent = displayValue;
this.style.setProperty('--value-percent', percent + '%');
this.style.setProperty('--value-width', '' + displayValue.length);
}
diff --git a/src/custom-els/RangeInput/missing-types.d.ts b/src/custom-els/RangeInput/missing-types.d.ts
index 41c48e40..d566207d 100644
--- a/src/custom-els/RangeInput/missing-types.d.ts
+++ b/src/custom-els/RangeInput/missing-types.d.ts
@@ -1,9 +1,5 @@
declare namespace JSX {
- interface RangeInputAttributes extends HTMLAttributes {
- reversed?: boolean;
- }
-
interface IntrinsicElements {
- 'range-input': RangeInputAttributes;
+ 'range-input': HTMLAttributes;
}
}
diff --git a/src/custom-els/RangeInput/styles.css b/src/custom-els/RangeInput/styles.css
index c0397d80..f48483b3 100644
--- a/src/custom-els/RangeInput/styles.css
+++ b/src/custom-els/RangeInput/styles.css
@@ -14,41 +14,6 @@ range-input[disabled] {
filter: grayscale(1);
}
-/* Reversed Variant */
-range-input[reversed] input,
-range-input[reversed]::before,
-range-input[reversed] > div {
- transform: scaleX(-1);
-}
-range-input[reversed] > div > span {
- transform: scaleX(-1) scale(.2);
-}
-range-input[reversed] input:focus + div span {
- transform: scaleX(-1) scale(1);
-}
-
-
-range-input input {
- position: relative;
- flex: 1;
- vertical-align: middle;
- width: 100%;
- padding: 0 !important;
- margin: 0 !important;
- background: none;
- appearance: none;
- -webkit-appearance: none;
- outline: none;
-}
-range-input input::-webkit-slider-runnable-track,
-range-input input::-moz-range-track,
-range-input input::-ms-track {
- appearance: none;
- -ms-appearance: none;
- -moz-appearance: none;
- -webkit-appearance: none;
-}
-
range-input::before {
content: '';
display: block;
@@ -57,32 +22,33 @@ range-input::before {
left: 0;
width: 100%;
height: 2px;
- outline: none;
- border: none;
- background: #eee;
border-radius: 1px;
box-shadow: 0 -.5px 0 rgba(0,0,0,0.3), inset 0 .5px 0 rgba(255,255,255,0.2), 0 .5px 0 rgba(255,255,255,0.3);
background: linear-gradient(#34B9EB, #218ab1) 0/ var(--value-percent, 0%) 100% no-repeat #eee;
}
-range-input input::-webkit-slider-thumb {
- appearance: none;
- -webkit-appearance: none;
- background: url('data:image/svg+xml,') center no-repeat #34B9EB;
- box-sizing: content-box;
+.input {
+ position: relative;
+ width: 100%;
+ padding: 0;
+ margin: 0;
+ opacity: 0;
+}
+
+.thumb {
+ pointer-events: none;
+ position: absolute;
+ bottom: 3px;
+ left: var(--value-percent, 0%);
+ margin-left: -6px;
+ background: url('data:image/svg+xml,') center no-repeat #34B9EB;
border-radius: 50%;
width: 12px;
height: 12px;
- border: none;
box-shadow: 0 0.5px 2px rgba(0,0,0,0.3);
- outline: none;
}
-range-input input:focus::-webkit-slider-thumb {
- box-shadow: 0 1px 3px rgba(0,0,0,0.5);
-}
-
-range-input > div {
+.thumb-wrapper {
position: absolute;
left: 6px;
right: 6px;
@@ -91,8 +57,8 @@ range-input > div {
overflow: visible;
}
-range-input > div > span {
- background: url('data:image/svg+xml,') top center no-repeat;
+.value-display {
+ background: url('data:image/svg+xml,') top center no-repeat;
position: absolute;
box-sizing: border-box;
left: var(--value-percent, 0%);
@@ -111,12 +77,18 @@ range-input > div > span {
font-size: calc(100% - var(--value-width, 3) / 5 * .2em);
text-overflow: clip;
text-shadow: 0 -.5px 0 rgba(0,0,0,0.4);
- transition: transform 200ms ease, opacity 200ms ease;
+ transition: all 200ms ease;
+ transition-property: opacity, transform;
will-change: transform;
pointer-events: none;
overflow: hidden;
}
-range-input input:focus + div span {
+
+.input:active + .thumb-wrapper .value-display {
opacity: 1;
transform: scale(1);
}
+
+.input:active + .thumb-wrapper .thumb {
+ box-shadow: 0 1px 3px rgba(0,0,0,0.5);
+}
diff --git a/src/lib/icons.tsx b/src/lib/icons.tsx
index 0562b8d9..06cb7dac 100644
--- a/src/lib/icons.tsx
+++ b/src/lib/icons.tsx
@@ -8,7 +8,7 @@ const Icon = (props: JSX.HTMLAttributes) => (
export const DownloadIcon = (props: JSX.HTMLAttributes) => (
-
+
);
@@ -29,3 +29,15 @@ export const RemoveIcon = (props: JSX.HTMLAttributes) => (
);
+
+export const UncheckedIcon = (props: JSX.HTMLAttributes) => (
+
+
+
+);
+
+export const CheckedIcon = (props: JSX.HTMLAttributes) => (
+
+
+
+);
diff --git a/src/lib/util.ts b/src/lib/util.ts
index 4e974782..cecf38fc 100644
--- a/src/lib/util.ts
+++ b/src/lib/util.ts
@@ -203,25 +203,40 @@ export function nativeResize(
/**
* @param field An HTMLInputElement, but the casting is done here to tidy up onChange.
+ * @param defaultVal Value to return if 'field' doesn't exist.
*/
-export function inputFieldValueAsNumber(field: any): number {
- return Number((field as HTMLInputElement).value);
+export function inputFieldValueAsNumber(field: any, defaultVal: number = 0): number {
+ if (!field) return defaultVal;
+ return Number(inputFieldValue(field));
}
/**
* @param field An HTMLInputElement, but the casting is done here to tidy up onChange.
+ * @param defaultVal Value to return if 'field' doesn't exist.
*/
-export function inputFieldCheckedAsNumber(field: any): number {
+export function inputFieldCheckedAsNumber(field: any, defaultVal: number = 0): number {
+ if (!field) return defaultVal;
return Number(inputFieldChecked(field));
}
/**
* @param field An HTMLInputElement, but the casting is done here to tidy up onChange.
+ * @param defaultVal Value to return if 'field' doesn't exist.
*/
-export function inputFieldChecked(field: any): boolean {
+export function inputFieldChecked(field: any, defaultVal: boolean = false): boolean {
+ if (!field) return defaultVal;
return (field as HTMLInputElement).checked;
}
+/**
+ * @param field An HTMLInputElement, but the casting is done here to tidy up onChange.
+ * @param defaultVal Value to return if 'field' doesn't exist.
+ */
+export function inputFieldValue(field: any, defaultVal: string = ''): string {
+ if (!field) return defaultVal;
+ return (field as HTMLInputElement).value;
+}
+
/**
* Creates a promise that resolves when the user types the konami code.
*/
diff --git a/src/style/index.scss b/src/style/index.scss
index a22b7362..27037998 100644
--- a/src/style/index.scss
+++ b/src/style/index.scss
@@ -1,7 +1,3 @@
-/*
-Note: These styles are temporary. They will be replaced before going live.
-*/
-
@import './reset.scss';
html, body {
diff --git a/src/style/reset.scss b/src/style/reset.scss
index 2d60b927..c863fb75 100644
--- a/src/style/reset.scss
+++ b/src/style/reset.scss
@@ -1,7 +1,3 @@
-/*
-Note: These styles are temporary. They will be replaced before going live.
-*/
-
button, a, img, input, select, textarea {
-webkit-tap-highlight-color: rgba(0,0,0,0);
}
diff --git a/webpack.config.js b/webpack.config.js
index dbbf146d..ff942630 100644
--- a/webpack.config.js
+++ b/webpack.config.js
@@ -23,7 +23,8 @@ module.exports = function (_, env) {
const nodeModules = path.join(__dirname, 'node_modules');
const componentStyleDirs = [
path.join(__dirname, 'src/components'),
- path.join(__dirname, 'src/codecs')
+ path.join(__dirname, 'src/codecs'),
+ path.join(__dirname, 'src/custom-els'),
];
return {