mevebu 1.3.0 → 1.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/mevebu/app/mevebu.js +260 -208
- package/mevebu/app/tailwind.css +1635 -2079
- package/mevebu/app/tailwind.src.css +155 -1127
- package/mevebu/index.html +2 -2
- package/mevebu/libs/js/core.js +235 -95
- package/mevebu/libs/js/tools/abstract-checklist.js +5 -7
- package/mevebu/libs/js/tools/add-subtract-days.js +9 -11
- package/mevebu/libs/js/tools/age-calculator.js +7 -8
- package/mevebu/libs/js/tools/api-request-builder.js +3 -4
- package/mevebu/libs/js/tools/area-converter.js +7 -9
- package/mevebu/libs/js/tools/aspect-ratio.js +8 -9
- package/mevebu/libs/js/tools/average-calculator.js +4 -5
- package/mevebu/libs/js/tools/base64.js +2 -3
- package/mevebu/libs/js/tools/binary-calculator.js +6 -7
- package/mevebu/libs/js/tools/border-radius.js +7 -9
- package/mevebu/libs/js/tools/box-shadow.js +6 -7
- package/mevebu/libs/js/tools/break-even.js +4 -5
- package/mevebu/libs/js/tools/business-days.js +7 -8
- package/mevebu/libs/js/tools/canonical-tag.js +5 -6
- package/mevebu/libs/js/tools/caption-counter.js +5 -7
- package/mevebu/libs/js/tools/case-converter.js +10 -11
- package/mevebu/libs/js/tools/case-inspector.js +14 -16
- package/mevebu/libs/js/tools/cash-denomination.js +7 -8
- package/mevebu/libs/js/tools/cash-flow.js +7 -8
- package/mevebu/libs/js/tools/cert-expiry.js +5 -7
- package/mevebu/libs/js/tools/changelog-generator.js +4 -6
- package/mevebu/libs/js/tools/character-counter.js +10 -13
- package/mevebu/libs/js/tools/checklist.js +6 -7
- package/mevebu/libs/js/tools/citation-number-check.js +10 -13
- package/mevebu/libs/js/tools/coin-flip.js +9 -11
- package/mevebu/libs/js/tools/color-converter.js +6 -7
- package/mevebu/libs/js/tools/color-palette.js +6 -8
- package/mevebu/libs/js/tools/color-picker.js +4 -5
- package/mevebu/libs/js/tools/content-calendar.js +3 -4
- package/mevebu/libs/js/tools/contrast-checker.js +5 -6
- package/mevebu/libs/js/tools/cron-builder.js +4 -6
- package/mevebu/libs/js/tools/css-clamp.js +5 -6
- package/mevebu/libs/js/tools/css-formatter.js +4 -7
- package/mevebu/libs/js/tools/css-grid.js +6 -7
- package/mevebu/libs/js/tools/css-minifier.js +5 -8
- package/mevebu/libs/js/tools/csv-to-json.js +2 -3
- package/mevebu/libs/js/tools/csv-viewer.js +9 -11
- package/mevebu/libs/js/tools/currency-format.js +5 -7
- package/mevebu/libs/js/tools/daily-planner.js +9 -10
- package/mevebu/libs/js/tools/data-masker.js +6 -8
- package/mevebu/libs/js/tools/data-storage.js +7 -9
- package/mevebu/libs/js/tools/data-validator.js +9 -11
- package/mevebu/libs/js/tools/date-difference.js +6 -7
- package/mevebu/libs/js/tools/date-format.js +11 -13
- package/mevebu/libs/js/tools/delimiter-converter.js +3 -5
- package/mevebu/libs/js/tools/discount-calculator.js +4 -5
- package/mevebu/libs/js/tools/doi-resolver.js +4 -5
- package/mevebu/libs/js/tools/env-formatter.js +3 -6
- package/mevebu/libs/js/tools/equation-formatter.js +5 -9
- package/mevebu/libs/js/tools/expense-split.js +6 -7
- package/mevebu/libs/js/tools/fake-data.js +8 -13
- package/mevebu/libs/js/tools/find-replace.js +3 -5
- package/mevebu/libs/js/tools/flashcard-creator.js +9 -10
- package/mevebu/libs/js/tools/font-pairing.js +6 -8
- package/mevebu/libs/js/tools/fraction-calculator.js +10 -13
- package/mevebu/libs/js/tools/git-command-builder.js +11 -22
- package/mevebu/libs/js/tools/gpa-calculator.js +8 -10
- package/mevebu/libs/js/tools/grade-calculator.js +7 -8
- package/mevebu/libs/js/tools/gradient-generator.js +10 -12
- package/mevebu/libs/js/tools/habit-tracker.js +8 -9
- package/mevebu/libs/js/tools/hash-generator.js +3 -5
- package/mevebu/libs/js/tools/hashtag-formatter.js +6 -8
- package/mevebu/libs/js/tools/hmac-generator.js +3 -5
- package/mevebu/libs/js/tools/hpp-calculator.js +4 -5
- package/mevebu/libs/js/tools/html-entity.js +2 -3
- package/mevebu/libs/js/tools/html-formatter.js +3 -6
- package/mevebu/libs/js/tools/http-status.js +5 -7
- package/mevebu/libs/js/tools/inventory-quantity.js +7 -8
- package/mevebu/libs/js/tools/json-formatter.js +3 -6
- package/mevebu/libs/js/tools/json-minifier.js +5 -8
- package/mevebu/libs/js/tools/json-to-csv.js +3 -5
- package/mevebu/libs/js/tools/json-to-typescript.js +2 -5
- package/mevebu/libs/js/tools/json-validator.js +2 -3
- package/mevebu/libs/js/tools/jwt-decoder.js +6 -7
- package/mevebu/libs/js/tools/jwt-expiry.js +5 -6
- package/mevebu/libs/js/tools/jwt-generator.js +4 -6
- package/mevebu/libs/js/tools/length-converter.js +7 -9
- package/mevebu/libs/js/tools/license-reference.js +10 -13
- package/mevebu/libs/js/tools/line-counter.js +5 -7
- package/mevebu/libs/js/tools/list-difference.js +5 -7
- package/mevebu/libs/js/tools/list-merger.js +4 -5
- package/mevebu/libs/js/tools/locale-number.js +5 -8
- package/mevebu/libs/js/tools/markdown-preview.js +5 -8
- package/mevebu/libs/js/tools/markdown-table.js +5 -7
- package/mevebu/libs/js/tools/markup-calculator.js +4 -5
- package/mevebu/libs/js/tools/meeting-agenda.js +8 -9
- package/mevebu/libs/js/tools/meta-preview.js +12 -13
- package/mevebu/libs/js/tools/meta-tags.js +6 -7
- package/mevebu/libs/js/tools/mime-types.js +5 -7
- package/mevebu/libs/js/tools/name-picker.js +2 -3
- package/mevebu/libs/js/tools/number-base-converter.js +9 -12
- package/mevebu/libs/js/tools/password-generator.js +9 -12
- package/mevebu/libs/js/tools/password-strength.js +21 -25
- package/mevebu/libs/js/tools/percent-to-fraction.js +6 -7
- package/mevebu/libs/js/tools/percentage-calculator.js +6 -9
- package/mevebu/libs/js/tools/percentage-change.js +4 -5
- package/mevebu/libs/js/tools/permutation-combination.js +9 -12
- package/mevebu/libs/js/tools/prefix-suffix.js +2 -3
- package/mevebu/libs/js/tools/priority-matrix.js +6 -10
- package/mevebu/libs/js/tools/profit-margin.js +4 -5
- package/mevebu/libs/js/tools/qr-generator.js +3 -4
- package/mevebu/libs/js/tools/qr-scanner.js +11 -12
- package/mevebu/libs/js/tools/quadratic-solver.js +7 -8
- package/mevebu/libs/js/tools/query-builder.js +2 -3
- package/mevebu/libs/js/tools/quiz-formatter.js +4 -6
- package/mevebu/libs/js/tools/ratio-calculator.js +7 -8
- package/mevebu/libs/js/tools/readability.js +12 -15
- package/mevebu/libs/js/tools/reading-time.js +5 -7
- package/mevebu/libs/js/tools/reference-completeness.js +6 -8
- package/mevebu/libs/js/tools/regex-tester.js +7 -10
- package/mevebu/libs/js/tools/remove-duplicate-lines.js +2 -3
- package/mevebu/libs/js/tools/remove-extra-spaces.js +3 -5
- package/mevebu/libs/js/tools/robots-txt.js +3 -4
- package/mevebu/libs/js/tools/roman-numeral-converter.js +6 -8
- package/mevebu/libs/js/tools/sales-commission.js +8 -9
- package/mevebu/libs/js/tools/scientific-calculator.js +10 -14
- package/mevebu/libs/js/tools/scientific-notation.js +8 -9
- package/mevebu/libs/js/tools/script-planner.js +8 -10
- package/mevebu/libs/js/tools/selling-price.js +4 -5
- package/mevebu/libs/js/tools/semver-helper.js +3 -5
- package/mevebu/libs/js/tools/sentence-length.js +9 -10
- package/mevebu/libs/js/tools/sequence-analyzer.js +7 -8
- package/mevebu/libs/js/tools/sitemap-xml.js +4 -7
- package/mevebu/libs/js/tools/slug-converter.js +3 -5
- package/mevebu/libs/js/tools/slug-preview.js +6 -7
- package/mevebu/libs/js/tools/sort-lines.js +3 -5
- package/mevebu/libs/js/tools/speed-converter.js +7 -9
- package/mevebu/libs/js/tools/spin-wheel.js +19 -17
- package/mevebu/libs/js/tools/sql-formatter.js +5 -9
- package/mevebu/libs/js/tools/sql-minifier.js +5 -8
- package/mevebu/libs/js/tools/standard-deviation.js +6 -9
- package/mevebu/libs/js/tools/stopwatch.js +4 -6
- package/mevebu/libs/js/tools/study-schedule.js +8 -9
- package/mevebu/libs/js/tools/subtitle-formatter.js +10 -11
- package/mevebu/libs/js/tools/table-to-markdown.js +8 -12
- package/mevebu/libs/js/tools/tax-calculator.js +4 -5
- package/mevebu/libs/js/tools/temperature-converter.js +8 -11
- package/mevebu/libs/js/tools/text-diff.js +11 -7
- package/mevebu/libs/js/tools/text-direction.js +3 -5
- package/mevebu/libs/js/tools/text-reverser.js +3 -5
- package/mevebu/libs/js/tools/time-duration.js +8 -9
- package/mevebu/libs/js/tools/timestamp-converter.js +4 -6
- package/mevebu/libs/js/tools/timezone-converter.js +9 -13
- package/mevebu/libs/js/tools/title-length.js +6 -7
- package/mevebu/libs/js/tools/unicode-inspector.js +4 -5
- package/mevebu/libs/js/tools/url-inspector.js +8 -9
- package/mevebu/libs/js/tools/url-parser.js +12 -13
- package/mevebu/libs/js/tools/utf8-bytes.js +6 -9
- package/mevebu/libs/js/tools/uuid-generator.js +5 -9
- package/mevebu/libs/js/tools/version-compare.js +3 -4
- package/mevebu/libs/js/tools/video-duration.js +5 -7
- package/mevebu/libs/js/tools/volume-converter.js +7 -9
- package/mevebu/libs/js/tools/weekly-planner.js +8 -10
- package/mevebu/libs/js/tools/weight-converter.js +7 -9
- package/mevebu/libs/js/tools/weighted-average.js +4 -5
- package/mevebu/libs/js/tools/word-counter.js +10 -13
- package/mevebu/libs/js/tools/word-limit.js +4 -5
- package/mevebu/libs/js/tools/yaml-formatter.js +6 -7
- package/package.json +1 -1
|
@@ -6,8 +6,7 @@ const UNITS = [
|
|
|
6
6
|
{ value: "kmh", label: "Kilometer per hour", factor: 0.2777777777777778 },
|
|
7
7
|
{ value: "mph", label: "Mile per hour", factor: 0.44704 },
|
|
8
8
|
{ value: "fps", label: "Foot per second", factor: 0.3048 },
|
|
9
|
-
{ value: "kn", label: "Knot", factor: 0.5144444444444445 }
|
|
10
|
-
];
|
|
9
|
+
{ value: "kn", label: "Knot", factor: 0.5144444444444445 }];
|
|
11
10
|
|
|
12
11
|
const byId = Object.fromEntries(UNITS.map((unit) => [unit.value, unit]));
|
|
13
12
|
const options = UNITS.map(({ value, label }) => ({ value, label }));
|
|
@@ -24,11 +23,11 @@ export function mount(slot) {
|
|
|
24
23
|
const value = numberInput({ value: "1", step: "any", "aria-label": "Value" });
|
|
25
24
|
const from = selectInput(options, { value: "kmh", "aria-label": "From unit" });
|
|
26
25
|
const to = selectInput(options, { value: "mph", "aria-label": "To unit" });
|
|
27
|
-
const result = el("span", { class: "
|
|
28
|
-
const stats = el("div", { class: "
|
|
29
|
-
const factor = el("p", { class: "
|
|
26
|
+
const result = el("span", { class: "wrap-anywhere text-xl font-bold tracking-[-0.02em] tabular-nums" }, "-");
|
|
27
|
+
const stats = el("div", { class: "grid gap-2.5 [grid-template-columns:repeat(auto-fit,minmax(120px,1fr))]" }, el("div", { class: "grid gap-0.5 rounded-r1 border border-orbit bg-deck-2 px-3.5 py-3" }, el("span", { class: "text-2xs font-semibold text-ink-3" }, "Result"), result));
|
|
28
|
+
const factor = el("p", { class: "border-s-[3px] border-transparent ps-2 text-sm text-ink-3" });
|
|
30
29
|
const error = notice("error");
|
|
31
|
-
const scroll = el("div", { class: "
|
|
30
|
+
const scroll = el("div", { class: "max-h-80 overflow-auto" });
|
|
32
31
|
let text = "";
|
|
33
32
|
|
|
34
33
|
function run() {
|
|
@@ -48,7 +47,7 @@ export function mount(slot) {
|
|
|
48
47
|
const out = (n * a.factor) / b.factor;
|
|
49
48
|
result.textContent = `${fmt(out)} ${b.value}`;
|
|
50
49
|
factor.textContent = `1 ${a.value} = ${fmt(a.factor / b.factor)} ${b.value}`;
|
|
51
|
-
scroll.replaceChildren(el("table", { class: "
|
|
50
|
+
scroll.replaceChildren(el("table", { class: "w-full border-collapse font-mono text-sm [&_th]:wrap-anywhere [&_td]:wrap-anywhere [&_th]:border-b [&_td]:border-b [&_th]:border-orbit [&_td]:border-orbit [&_th]:px-2.5 [&_td]:px-2.5 [&_th]:py-2 [&_td]:py-2 [&_th]:text-start [&_td]:text-start [&_th]:font-sans [&_th]:text-2xs [&_th]:font-semibold [&_th]:text-ink-2 [&_tbody>tr:last-child>th]:border-b-0 [&_tbody>tr:last-child>td]:border-b-0" },
|
|
52
51
|
el("thead", {}, el("tr", {}, el("th", {}, "Unit"), el("th", {}, "Value"))),
|
|
53
52
|
el("tbody", {}, ...UNITS.map((unit) => el("tr", {},
|
|
54
53
|
el("td", {}, unit.label),
|
|
@@ -72,8 +71,7 @@ export function mount(slot) {
|
|
|
72
71
|
stack(panel("Value", value), panel("From", from), panel("To", to), error),
|
|
73
72
|
stack(panel("Result", stats), factor, panel("All units", scroll))
|
|
74
73
|
),
|
|
75
|
-
foot: actions(swap, copyButton(() => text, "Copy result", "fill"))
|
|
76
|
-
});
|
|
74
|
+
foot: actions(swap, copyButton(() => text, "Copy result", "fill"))});
|
|
77
75
|
slot.append(card.node);
|
|
78
76
|
run();
|
|
79
77
|
|
|
@@ -2,7 +2,9 @@
|
|
|
2
2
|
// spin, driven by the browser random source, so every option has equal odds.
|
|
3
3
|
import { el, toast, copyButton, randomInt, bench, stage, stack, panel, textArea, button, readPin, writePin, toolcard, actions } from "../core.js";
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
// The slice palette. A green ramp that alternates dark and light, so two
|
|
6
|
+
// neighbouring slices never read as one wedge and every label stays legible.
|
|
7
|
+
const SEGMENTS = ["#0f6b4a", "#e0f0e7", "#0a5138", "#bfcac1", "#35805f", "#1d3b2c", "#8fd0b3", "#0b2a1e"];
|
|
6
8
|
|
|
7
9
|
/**
|
|
8
10
|
* The slice label, in whichever ink reads better on that fill. Every fill in the
|
|
@@ -12,7 +14,7 @@ const SEGMENTS = ["#2456D6", "#151D2E", "#5F8BFF", "#4B5871", "#1A43AD", "#69748
|
|
|
12
14
|
function textOn(hex) {
|
|
13
15
|
const linear = (value) => (value <= 0.03928 ? value / 12.92 : ((value + 0.055) / 1.055) ** 2.4);
|
|
14
16
|
const [r, g, b] = [1, 3, 5].map((at) => linear(parseInt(hex.slice(at, at + 2), 16) / 255));
|
|
15
|
-
return 0.2126 * r + 0.7152 * g + 0.0722 * b > 0.2056 ? "#
|
|
17
|
+
return 0.2126 * r + 0.7152 * g + 0.0722 * b > 0.2056 ? "#101a15" : "#FFFFFF";
|
|
16
18
|
}
|
|
17
19
|
|
|
18
20
|
function entriesFrom(text) {
|
|
@@ -24,15 +26,15 @@ export function mount(slot) {
|
|
|
24
26
|
const saved = readPin(PIN, { entries: "Ana\nBudi\nCitra\nDimas\nElena\nFajar" });
|
|
25
27
|
|
|
26
28
|
const listField = textArea({ value: saved.entries ?? "", "aria-label": "Options, one per line", placeholder: "One option per line" });
|
|
27
|
-
const canvas = el("canvas", { class: "
|
|
29
|
+
const canvas = el("canvas", { class: "h-auto w-[min(320px,100%)]", width: 360, height: 360, role: "img", "aria-label": "The wheel" });
|
|
28
30
|
const context = canvas.getContext("2d");
|
|
29
|
-
const historyBox = el("div", { class: "
|
|
31
|
+
const historyBox = el("div", { class: "flex max-w-[340px] flex-wrap justify-center gap-1.5", role: "list" });
|
|
30
32
|
|
|
31
33
|
const spinButton = button("Spin", { icon: "rotate-cw", kind: "fill" });
|
|
32
34
|
const clearButton = button("Clear past picks", { icon: "trash", kind: "quiet" });
|
|
33
35
|
const copyWinner = copyButton(() => winner, "Copy winner");
|
|
34
36
|
|
|
35
|
-
const status = el("div", { class: "
|
|
37
|
+
const status = el("div", { class: "text-balance text-xl font-bold tracking-[-0.01em]", "aria-live": "polite" }, "Add options, then spin.");
|
|
36
38
|
|
|
37
39
|
let rotation = 0;
|
|
38
40
|
let busy = false;
|
|
@@ -45,15 +47,17 @@ export function mount(slot) {
|
|
|
45
47
|
const center = size / 2;
|
|
46
48
|
const radius = center - 12;
|
|
47
49
|
const root = getComputedStyle(document.documentElement);
|
|
48
|
-
const ground = root.getPropertyValue("--
|
|
49
|
-
const orbit = root.getPropertyValue("--
|
|
50
|
-
const faint = root.getPropertyValue("--
|
|
51
|
-
const
|
|
50
|
+
const ground = root.getPropertyValue("--void").trim() || "#fff";
|
|
51
|
+
const orbit = root.getPropertyValue("--orbit-hard").trim() || "#888";
|
|
52
|
+
const faint = root.getPropertyValue("--ink-3").trim() || "#888";
|
|
53
|
+
const jade = root.getPropertyValue("--jade").trim() || "#0f6b4a";
|
|
54
|
+
// Canvas text is outside the stylesheet, so it reads the same stack the page uses.
|
|
55
|
+
const stack = root.getPropertyValue("--sans").trim() || "system-ui, sans-serif";
|
|
52
56
|
|
|
53
57
|
context.clearRect(0, 0, size, size);
|
|
54
58
|
if (entries.length < 2) {
|
|
55
59
|
context.fillStyle = faint;
|
|
56
|
-
context.font =
|
|
60
|
+
context.font = `15px ${stack}`;
|
|
57
61
|
context.textAlign = "center";
|
|
58
62
|
context.fillText(entries.length ? "Add one more option to spin" : "Type options to spin", center, center);
|
|
59
63
|
status.textContent = entries.length ? "One more option to go." : "Add options, then spin.";
|
|
@@ -73,7 +77,7 @@ export function mount(slot) {
|
|
|
73
77
|
context.rotate(a0 + slice / 2);
|
|
74
78
|
context.textAlign = "right";
|
|
75
79
|
context.fillStyle = textOn(SEGMENTS[i % SEGMENTS.length]);
|
|
76
|
-
context.font =
|
|
80
|
+
context.font = `600 13px ${stack}`;
|
|
77
81
|
const label = entry.length > 13 ? `${entry.slice(0, 12)}…` : entry;
|
|
78
82
|
context.fillText(label, radius - 12, 4);
|
|
79
83
|
context.restore();
|
|
@@ -90,7 +94,7 @@ export function mount(slot) {
|
|
|
90
94
|
context.lineTo(center + 8, 2);
|
|
91
95
|
context.lineTo(center, 20);
|
|
92
96
|
context.closePath();
|
|
93
|
-
context.fillStyle =
|
|
97
|
+
context.fillStyle = jade;
|
|
94
98
|
context.fill();
|
|
95
99
|
if (status.dataset.winner !== "1") status.textContent = "Add options, then spin.";
|
|
96
100
|
}
|
|
@@ -123,7 +127,7 @@ export function mount(slot) {
|
|
|
123
127
|
status.dataset.winner = "1";
|
|
124
128
|
winner = entries[winnerIndex];
|
|
125
129
|
status.textContent = winner;
|
|
126
|
-
const chip = el("span", { class: "
|
|
130
|
+
const chip = el("span", { class: "rounded-full border border-orbit bg-deck-2 px-2.5 py-1 text-2xs font-semibold", role: "listitem" }, winner);
|
|
127
131
|
historyBox.prepend(chip);
|
|
128
132
|
while (historyBox.children.length > 12) historyBox.lastChild.remove();
|
|
129
133
|
}
|
|
@@ -150,8 +154,7 @@ export function mount(slot) {
|
|
|
150
154
|
stack(panel("Options, one per line", listField)),
|
|
151
155
|
stage(canvas, status, historyBox)
|
|
152
156
|
),
|
|
153
|
-
foot: actions(copyWinner, clearButton, spinButton)
|
|
154
|
-
});
|
|
157
|
+
foot: actions(copyWinner, clearButton, spinButton)});
|
|
155
158
|
slot.append(card.node);
|
|
156
159
|
draw();
|
|
157
160
|
|
|
@@ -159,8 +162,7 @@ export function mount(slot) {
|
|
|
159
162
|
unmount() {
|
|
160
163
|
cancelAnimationFrame(frame);
|
|
161
164
|
busy = false;
|
|
162
|
-
}
|
|
163
|
-
};
|
|
165
|
+
}};
|
|
164
166
|
}
|
|
165
167
|
|
|
166
168
|
export default { mount };
|
|
@@ -4,8 +4,7 @@ import { bench, stack, panel, textArea, notice, switchControl, copyButton, actio
|
|
|
4
4
|
const CLAUSES = [
|
|
5
5
|
"SELECT", "FROM", "WHERE", "GROUP BY", "ORDER BY", "HAVING", "LIMIT", "OFFSET", "VALUES", "SET",
|
|
6
6
|
"INSERT INTO", "UPDATE", "DELETE FROM", "INNER JOIN", "LEFT JOIN", "RIGHT JOIN", "FULL JOIN",
|
|
7
|
-
"CROSS JOIN", "JOIN", "UNION ALL", "UNION", "CREATE TABLE", "ALTER TABLE", "DROP TABLE", "WITH", "RETURNING"
|
|
8
|
-
];
|
|
7
|
+
"CROSS JOIN", "JOIN", "UNION ALL", "UNION", "CREATE TABLE", "ALTER TABLE", "DROP TABLE", "WITH", "RETURNING"];
|
|
9
8
|
const WORDS = [...CLAUSES, "AND", "OR", "ON", "AS", "IN", "IS", "NOT", "NULL", "LIKE", "BETWEEN", "DISTINCT", "ASC", "DESC", "CASE", "WHEN", "THEN", "ELSE", "END"];
|
|
10
9
|
|
|
11
10
|
const toPattern = (word) => word.replace(/ /g, "\\s+");
|
|
@@ -33,18 +32,16 @@ function format(sql, upper) {
|
|
|
33
32
|
out = out.replace(clauseRe, "\n$1");
|
|
34
33
|
out = out.replace(/\s*\b(AND|OR)\b/gi, "\n $1");
|
|
35
34
|
out = out.replace(/\(\s+/g, "(").replace(/\s+\)/g, ")");
|
|
36
|
-
out = out.replace(/\n{3
|
|
35
|
+
out = out.replace(/\n{3}/g, "\n\n").trim();
|
|
37
36
|
return restore(out, store);
|
|
38
37
|
}
|
|
39
38
|
|
|
40
39
|
export function mount(slot) {
|
|
41
40
|
const source = textArea({
|
|
42
41
|
rows: 12,
|
|
43
|
-
class: "mvb-in mvb-in--area mvb-in--mono",
|
|
44
42
|
"aria-label": "SQL",
|
|
45
|
-
value: "select id, name from users where active = true and age > 18 order by name"
|
|
46
|
-
});
|
|
47
|
-
const result = textArea({ rows: 12, readonly: true, class: "mvb-in mvb-in--area mvb-in--mono", "aria-label": "Formatted SQL" });
|
|
43
|
+
value: "select id, name from users where active = true and age > 18 order by name"});
|
|
44
|
+
const result = textArea({ rows: 12, readonly: true, "aria-label": "Formatted SQL" });
|
|
48
45
|
const error = notice("error");
|
|
49
46
|
let upper = true;
|
|
50
47
|
|
|
@@ -67,8 +64,7 @@ export function mount(slot) {
|
|
|
67
64
|
stack(panel("Options", casing), panel("SQL", source), error),
|
|
68
65
|
stack(panel("Formatted", result))
|
|
69
66
|
),
|
|
70
|
-
foot: actions(copyButton(() => result.value, "Copy query", "fill"))
|
|
71
|
-
});
|
|
67
|
+
foot: actions(copyButton(() => result.value, "Copy query", "fill"))});
|
|
72
68
|
slot.append(card.node);
|
|
73
69
|
run();
|
|
74
70
|
|
|
@@ -4,7 +4,7 @@ import { el, bench, stack, panel, textArea, switchControl, copyButton, actions,
|
|
|
4
4
|
const bytes = (text) => new TextEncoder().encode(text).length;
|
|
5
5
|
|
|
6
6
|
const tile = (key, value) =>
|
|
7
|
-
el("div", { class: "
|
|
7
|
+
el("div", { class: "grid gap-0.5 rounded-r1 border border-orbit bg-deck-2 px-3.5 py-3" }, el("span", { class: "wrap-anywhere text-xl font-bold tracking-[-0.02em] tabular-nums" }, value), el("span", { class: "text-2xs font-semibold text-ink-3" }, key));
|
|
8
8
|
|
|
9
9
|
function minify(sql, stripComments) {
|
|
10
10
|
let out = "";
|
|
@@ -59,12 +59,10 @@ function minify(sql, stripComments) {
|
|
|
59
59
|
export function mount(slot) {
|
|
60
60
|
const source = textArea({
|
|
61
61
|
rows: 12,
|
|
62
|
-
class: "mvb-in mvb-in--area mvb-in--mono",
|
|
63
62
|
"aria-label": "SQL",
|
|
64
|
-
value: "SELECT id,\n name\n FROM users\n WHERE active = true\n AND age > 18;"
|
|
65
|
-
});
|
|
66
|
-
const
|
|
67
|
-
const stats = el("div", { class: "mvb-stats" });
|
|
63
|
+
value: "SELECT id,\n name\n FROM users\n WHERE active = true\n AND age > 18;"});
|
|
64
|
+
const result = textArea({ rows: 12, readonly: true, "aria-label": "Minified SQL" });
|
|
65
|
+
const stats = el("div", { class: "grid gap-2.5 [grid-template-columns:repeat(auto-fit,minmax(120px,1fr))]" });
|
|
68
66
|
let stripComments = true;
|
|
69
67
|
|
|
70
68
|
const comments = switchControl("Remove comments", { checked: true });
|
|
@@ -91,8 +89,7 @@ export function mount(slot) {
|
|
|
91
89
|
stack(panel("Options", comments), panel("SQL", source)),
|
|
92
90
|
stack(panel("Minified", result), stats)
|
|
93
91
|
),
|
|
94
|
-
foot: actions(copyButton(() => result.value, "Copy query", "fill"))
|
|
95
|
-
});
|
|
92
|
+
foot: actions(copyButton(() => result.value, "Copy query", "fill"))});
|
|
96
93
|
slot.append(card.node);
|
|
97
94
|
run();
|
|
98
95
|
|
|
@@ -12,8 +12,8 @@ function parseNumbers(text) {
|
|
|
12
12
|
}
|
|
13
13
|
|
|
14
14
|
function tile(label) {
|
|
15
|
-
const value = el("span", { class: "
|
|
16
|
-
const node = el("div", { class: "
|
|
15
|
+
const value = el("span", { class: "wrap-anywhere text-xl font-bold tracking-[-0.02em] tabular-nums" }, "-");
|
|
16
|
+
const node = el("div", { class: "grid gap-0.5 rounded-r1 border border-orbit bg-deck-2 px-3.5 py-3" }, value, el("span", { class: "text-2xs font-semibold text-ink-3" }, label));
|
|
17
17
|
node.set = (text) => { value.textContent = text; };
|
|
18
18
|
return node;
|
|
19
19
|
}
|
|
@@ -24,7 +24,7 @@ export function mount(slot) {
|
|
|
24
24
|
const variance = tile("Variance");
|
|
25
25
|
const deviation = tile("Std deviation");
|
|
26
26
|
const count = tile("Count");
|
|
27
|
-
const stats = el("div", { class: "
|
|
27
|
+
const stats = el("div", { class: "grid gap-2.5 [grid-template-columns:repeat(auto-fit,minmax(120px,1fr))]" }, mean, variance, deviation, count);
|
|
28
28
|
const error = notice("error");
|
|
29
29
|
let kind = "population";
|
|
30
30
|
let text = "";
|
|
@@ -66,16 +66,14 @@ export function mount(slot) {
|
|
|
66
66
|
const chips = chipRow(
|
|
67
67
|
[
|
|
68
68
|
{ value: "population", label: "Population" },
|
|
69
|
-
{ value: "sample", label: "Sample" },
|
|
70
|
-
],
|
|
69
|
+
{ value: "sample", label: "Sample" }],
|
|
71
70
|
{
|
|
72
71
|
value: kind,
|
|
73
72
|
label: "Data set",
|
|
74
73
|
onChange: (value) => {
|
|
75
74
|
kind = value;
|
|
76
75
|
run();
|
|
77
|
-
}
|
|
78
|
-
}
|
|
76
|
+
}}
|
|
79
77
|
);
|
|
80
78
|
|
|
81
79
|
source.addEventListener("input", run);
|
|
@@ -85,8 +83,7 @@ export function mount(slot) {
|
|
|
85
83
|
stack(panel("Numbers", source), panel("Data set", chips), error),
|
|
86
84
|
stack(stats)
|
|
87
85
|
),
|
|
88
|
-
foot: actions(copyButton(() => text, "Copy summary", "fill"))
|
|
89
|
-
});
|
|
86
|
+
foot: actions(copyButton(() => text, "Copy summary", "fill"))});
|
|
90
87
|
slot.append(card.node);
|
|
91
88
|
run();
|
|
92
89
|
|
|
@@ -15,7 +15,7 @@ function format(ms) {
|
|
|
15
15
|
|
|
16
16
|
export function mount(slot) {
|
|
17
17
|
const display = output({ rows: 2 });
|
|
18
|
-
const listWrap = el("div", { class: "
|
|
18
|
+
const listWrap = el("div", { class: "max-h-80 overflow-auto" });
|
|
19
19
|
|
|
20
20
|
const startButton = button("Start", { icon: "play", kind: "fill" });
|
|
21
21
|
const lapButton = button("Lap", { icon: "plus" });
|
|
@@ -59,7 +59,7 @@ export function mount(slot) {
|
|
|
59
59
|
el("td", {}, format(total))
|
|
60
60
|
);
|
|
61
61
|
});
|
|
62
|
-
listWrap.replaceChildren(el("table", { class: "
|
|
62
|
+
listWrap.replaceChildren(el("table", { class: "w-full border-collapse font-mono text-sm [&_th]:wrap-anywhere [&_td]:wrap-anywhere [&_th]:border-b [&_td]:border-b [&_th]:border-orbit [&_td]:border-orbit [&_th]:px-2.5 [&_td]:px-2.5 [&_th]:py-2 [&_td]:py-2 [&_th]:text-start [&_td]:text-start [&_th]:font-sans [&_th]:text-2xs [&_th]:font-semibold [&_th]:text-ink-2 [&_tbody>tr:last-child>th]:border-b-0 [&_tbody>tr:last-child>td]:border-b-0" },
|
|
63
63
|
el("thead", {}, el("tr", {}, el("th", {}, "#"), el("th", {}, "Lap"), el("th", {}, "Total"))),
|
|
64
64
|
el("tbody", {}, ...rows)
|
|
65
65
|
));
|
|
@@ -115,8 +115,7 @@ export function mount(slot) {
|
|
|
115
115
|
stack(panel("Laps", listWrap)),
|
|
116
116
|
stack(display)
|
|
117
117
|
),
|
|
118
|
-
foot: actions(copyLaps, resetButton, lapButton, startButton)
|
|
119
|
-
});
|
|
118
|
+
foot: actions(copyLaps, resetButton, lapButton, startButton)});
|
|
120
119
|
slot.append(card.node);
|
|
121
120
|
paint();
|
|
122
121
|
renderLaps();
|
|
@@ -126,8 +125,7 @@ export function mount(slot) {
|
|
|
126
125
|
unmount() {
|
|
127
126
|
cancelAnimationFrame(frame);
|
|
128
127
|
running = false;
|
|
129
|
-
}
|
|
130
|
-
};
|
|
128
|
+
}};
|
|
131
129
|
}
|
|
132
130
|
|
|
133
131
|
export default { mount };
|
|
@@ -9,16 +9,16 @@ export function mount(slot) {
|
|
|
9
9
|
let list = Array.isArray(saved) ? saved : [];
|
|
10
10
|
|
|
11
11
|
const name = textInput({ placeholder: "Topic", "aria-label": "Topic" });
|
|
12
|
-
const date = el("input", { class: "
|
|
13
|
-
const listWrap = el("div", { class: "
|
|
14
|
-
const stats = el("div", { class: "
|
|
12
|
+
const date = el("input", { class: "min-w-0 rounded-r1 border border-orbit bg-deck text-ink transition-[border-color,box-shadow] duration-150 placeholder:text-ink-3 hover:border-orbit-hard focus:border-jade focus:outline-none focus:ring-3 focus:ring-jade-soft w-full min-h-[var(--ctl)] px-3 py-2.5 text-base", type: "date", "aria-label": "Session date" });
|
|
13
|
+
const listWrap = el("div", { class: "max-h-80 overflow-auto" });
|
|
14
|
+
const stats = el("div", { class: "grid gap-2.5 [grid-template-columns:repeat(auto-fit,minmax(120px,1fr))]" });
|
|
15
15
|
|
|
16
16
|
const addButton = button("Add", { icon: "plus", kind: "fill" });
|
|
17
17
|
|
|
18
18
|
const save = () => writePin(PIN, list);
|
|
19
|
-
const tile = (key, value) => el("div", { class: "
|
|
20
|
-
el("span", { class: "
|
|
21
|
-
el("span", { class: "
|
|
19
|
+
const tile = (key, value) => el("div", { class: "grid gap-0.5 rounded-r1 border border-orbit bg-deck-2 px-3.5 py-3" },
|
|
20
|
+
el("span", { class: "wrap-anywhere text-xl font-bold tracking-[-0.02em] tabular-nums" }, String(value)),
|
|
21
|
+
el("span", { class: "text-2xs font-semibold text-ink-3" }, key));
|
|
22
22
|
|
|
23
23
|
const sorted = () => list.slice().sort((a, b) => (a.date || "9999").localeCompare(b.date || "9999"));
|
|
24
24
|
|
|
@@ -50,7 +50,7 @@ export function mount(slot) {
|
|
|
50
50
|
if (!rows.length) {
|
|
51
51
|
listWrap.replaceChildren(blank("calendar-days", "No topics yet."));
|
|
52
52
|
} else {
|
|
53
|
-
listWrap.replaceChildren(el("table", { class: "
|
|
53
|
+
listWrap.replaceChildren(el("table", { class: "w-full border-collapse font-mono text-sm [&_th]:wrap-anywhere [&_td]:wrap-anywhere [&_th]:border-b [&_td]:border-b [&_th]:border-orbit [&_td]:border-orbit [&_th]:px-2.5 [&_td]:px-2.5 [&_th]:py-2 [&_td]:py-2 [&_th]:text-start [&_td]:text-start [&_th]:font-sans [&_th]:text-2xs [&_th]:font-semibold [&_th]:text-ink-2 [&_tbody>tr:last-child>th]:border-b-0 [&_tbody>tr:last-child>td]:border-b-0" },
|
|
54
54
|
el("thead", {}, el("tr", {}, el("th", {}, "Topic"), el("th", {}, "Date"), el("th", {}, ""))),
|
|
55
55
|
el("tbody", {}, ...rows.map(rowNode))));
|
|
56
56
|
}
|
|
@@ -77,8 +77,7 @@ export function mount(slot) {
|
|
|
77
77
|
stack(panel("Topic", name), panel("Session date", date)),
|
|
78
78
|
stack(stats, panel("Plan", listWrap))
|
|
79
79
|
),
|
|
80
|
-
foot: actions(copyButton(() => sorted().map((item) => `${item.date || "No date"} ${item.done ? "[x]" : "[ ]"} ${item.name}`).join("\n"), "Copy plan"), addButton)
|
|
81
|
-
});
|
|
80
|
+
foot: actions(copyButton(() => sorted().map((item) => `${item.date || "No date"} ${item.done ? "[x]" : "[ ]"} ${item.name}`).join("\n"), "Copy plan"), addButton)});
|
|
82
81
|
slot.append(card.node);
|
|
83
82
|
render();
|
|
84
83
|
|
|
@@ -5,8 +5,8 @@ const isTiming = (line) => line.includes("-->");
|
|
|
5
5
|
const isIndex = (line) => /^\d+$/.test(line.trim());
|
|
6
6
|
|
|
7
7
|
function tile(label) {
|
|
8
|
-
const value = el("span", { class: "
|
|
9
|
-
const node = el("div", { class: "
|
|
8
|
+
const value = el("span", { class: "wrap-anywhere text-xl font-bold tracking-[-0.02em] tabular-nums" }, "0");
|
|
9
|
+
const node = el("div", { class: "grid gap-0.5 rounded-r1 border border-orbit bg-deck-2 px-3.5 py-3" }, value, el("span", { class: "text-2xs font-semibold text-ink-3" }, label));
|
|
10
10
|
node.set = (text) => { value.textContent = text; };
|
|
11
11
|
return node;
|
|
12
12
|
}
|
|
@@ -18,9 +18,9 @@ export function mount(slot) {
|
|
|
18
18
|
const linesTile = tile("Lines");
|
|
19
19
|
const longTile = tile("Long lines");
|
|
20
20
|
const longestTile = tile("Longest");
|
|
21
|
-
const stats = el("div", { class: "
|
|
21
|
+
const stats = el("div", { class: "grid gap-2.5 [grid-template-columns:repeat(auto-fit,minmax(120px,1fr))]" }, linesTile, longTile, longestTile);
|
|
22
22
|
|
|
23
|
-
const longWrap = el("div", { class: "
|
|
23
|
+
const longWrap = el("div", { class: "max-h-80 overflow-auto" });
|
|
24
24
|
const out = output({ rows: 10 });
|
|
25
25
|
const note = notice("note");
|
|
26
26
|
|
|
@@ -41,16 +41,16 @@ export function mount(slot) {
|
|
|
41
41
|
if (!long.length) {
|
|
42
42
|
longWrap.replaceChildren(blank("film", "No line runs past the limit."));
|
|
43
43
|
} else {
|
|
44
|
-
longWrap.replaceChildren(el("div", { class: "
|
|
45
|
-
...long.map((line) => el("div", { class: "
|
|
46
|
-
el("span", { class: "
|
|
47
|
-
el("span", { class: "
|
|
44
|
+
longWrap.replaceChildren(el("div", { class: "grid gap-px overflow-hidden rounded-r1 border border-orbit bg-orbit" },
|
|
45
|
+
...long.map((line) => el("div", { class: "grid gap-3 bg-deck px-3 py-[9px] [grid-template-columns:minmax(90px,34%)_minmax(0,1fr)]" },
|
|
46
|
+
el("span", { class: "wrap-anywhere text-2xs font-semibold text-ink-2" }, `${line.length}`),
|
|
47
|
+
el("span", { class: "wrap-anywhere font-mono text-2xs" }, line)))));
|
|
48
48
|
}
|
|
49
49
|
|
|
50
50
|
tidied = raw
|
|
51
51
|
.map((line) => line.replace(/\s+$/, ""))
|
|
52
52
|
.join("\n")
|
|
53
|
-
.replace(/\n{3
|
|
53
|
+
.replace(/\n{3}/g, "\n\n")
|
|
54
54
|
.replace(/^\n+|\n+$/g, "");
|
|
55
55
|
out.write(tidied);
|
|
56
56
|
}
|
|
@@ -63,8 +63,7 @@ export function mount(slot) {
|
|
|
63
63
|
stack(panel("Subtitles", source), panel("Maximum line length", max), note),
|
|
64
64
|
stack(stats, panel("Long lines", longWrap), panel("Tidied", out))
|
|
65
65
|
),
|
|
66
|
-
foot: actions(copyButton(() => tidied, "Copy text", "fill"))
|
|
67
|
-
});
|
|
66
|
+
foot: actions(copyButton(() => tidied, "Copy text", "fill"))});
|
|
68
67
|
slot.append(card.node);
|
|
69
68
|
run();
|
|
70
69
|
|
|
@@ -55,32 +55,30 @@ function detectDelimiter(text) {
|
|
|
55
55
|
const escapeCell = (cell) => String(cell ?? "").replace(/\|/g, "\\|").replace(/\s*\n\s*/g, " ").trim();
|
|
56
56
|
|
|
57
57
|
function tile(label) {
|
|
58
|
-
const value = el("span", { class: "
|
|
59
|
-
const node = el("div", { class: "
|
|
58
|
+
const value = el("span", { class: "wrap-anywhere text-xl font-bold tracking-[-0.02em] tabular-nums" }, "0");
|
|
59
|
+
const node = el("div", { class: "grid gap-0.5 rounded-r1 border border-orbit bg-deck-2 px-3.5 py-3" }, value, el("span", { class: "text-2xs font-semibold text-ink-3" }, label));
|
|
60
60
|
node.set = (text) => { value.textContent = text; };
|
|
61
61
|
return node;
|
|
62
62
|
}
|
|
63
63
|
|
|
64
64
|
export function mount(slot) {
|
|
65
|
-
const source = textArea({ rows: 12,
|
|
65
|
+
const source = textArea({ rows: 12, "aria-label": "Table data", placeholder: "Paste rows copied from a spreadsheet" });
|
|
66
66
|
const delimiter = selectInput([
|
|
67
67
|
{ value: "auto", label: "Auto" },
|
|
68
68
|
{ value: ",", label: "Comma" },
|
|
69
69
|
{ value: ";", label: "Semicolon" },
|
|
70
70
|
{ value: "\t", label: "Tab" },
|
|
71
|
-
{ value: "|", label: "Pipe" },
|
|
72
|
-
], { value: "auto", "aria-label": "Delimiter" });
|
|
71
|
+
{ value: "|", label: "Pipe" }], { value: "auto", "aria-label": "Delimiter" });
|
|
73
72
|
const alignment = selectInput([
|
|
74
73
|
{ value: "none", label: "None" },
|
|
75
74
|
{ value: "left", label: "Left" },
|
|
76
75
|
{ value: "center", label: "Center" },
|
|
77
|
-
{ value: "right", label: "Right" },
|
|
78
|
-
], { value: "none", "aria-label": "Alignment" });
|
|
76
|
+
{ value: "right", label: "Right" }], { value: "none", "aria-label": "Alignment" });
|
|
79
77
|
|
|
80
78
|
const result = output({ rows: 12 });
|
|
81
79
|
const rowsTile = tile("Rows");
|
|
82
80
|
const colsTile = tile("Columns");
|
|
83
|
-
const stats = el("div", { class: "
|
|
81
|
+
const stats = el("div", { class: "grid gap-2.5 [grid-template-columns:repeat(auto-fit,minmax(120px,1fr))]" }, rowsTile, colsTile);
|
|
84
82
|
const error = notice("error");
|
|
85
83
|
let current = "";
|
|
86
84
|
|
|
@@ -112,8 +110,7 @@ export function mount(slot) {
|
|
|
112
110
|
current = [
|
|
113
111
|
line(header.map(escapeCell)),
|
|
114
112
|
line(separator(header.length)),
|
|
115
|
-
...body.map((row) => line(header.map((_, i) => escapeCell(row[i]))))
|
|
116
|
-
].join("\n");
|
|
113
|
+
...body.map((row) => line(header.map((_, i) => escapeCell(row[i]))))].join("\n");
|
|
117
114
|
result.write(current);
|
|
118
115
|
}
|
|
119
116
|
|
|
@@ -126,8 +123,7 @@ export function mount(slot) {
|
|
|
126
123
|
stack(panel("Table data", source), panel("Delimiter", delimiter), panel("Alignment", alignment), error),
|
|
127
124
|
stack(stats, panel("Markdown", result))
|
|
128
125
|
),
|
|
129
|
-
foot: actions(copyButton(() => current, "Copy Markdown", "fill"))
|
|
130
|
-
});
|
|
126
|
+
foot: actions(copyButton(() => current, "Copy Markdown", "fill"))});
|
|
131
127
|
slot.append(card.node);
|
|
132
128
|
run();
|
|
133
129
|
|
|
@@ -6,8 +6,8 @@ function fmt(n) {
|
|
|
6
6
|
}
|
|
7
7
|
|
|
8
8
|
function tile(label) {
|
|
9
|
-
const value = el("span", { class: "
|
|
10
|
-
const node = el("div", { class: "
|
|
9
|
+
const value = el("span", { class: "wrap-anywhere text-xl font-bold tracking-[-0.02em] tabular-nums" }, "-");
|
|
10
|
+
const node = el("div", { class: "grid gap-0.5 rounded-r1 border border-orbit bg-deck-2 px-3.5 py-3" }, value, el("span", { class: "text-2xs font-semibold text-ink-3" }, label));
|
|
11
11
|
node.set = (text) => { value.textContent = text; };
|
|
12
12
|
return node;
|
|
13
13
|
}
|
|
@@ -23,7 +23,7 @@ export function mount(slot) {
|
|
|
23
23
|
const taxTile = tile("Tax");
|
|
24
24
|
const netTile = tile("Net");
|
|
25
25
|
const grossTile = tile("Total");
|
|
26
|
-
const stats = el("div", { class: "
|
|
26
|
+
const stats = el("div", { class: "grid gap-2.5 [grid-template-columns:repeat(auto-fit,minmax(120px,1fr))]" }, taxTile, netTile, grossTile);
|
|
27
27
|
const error = notice("error");
|
|
28
28
|
const note = notice("note");
|
|
29
29
|
note.write("You set the rate. No official rule is applied.");
|
|
@@ -73,8 +73,7 @@ export function mount(slot) {
|
|
|
73
73
|
stack(panel("Amount", amount), panel("Amount type", chips), panel("Tax rate %", rate), error),
|
|
74
74
|
stack(stats, note)
|
|
75
75
|
),
|
|
76
|
-
foot: actions(copyButton(() => text, "Copy result", "fill"))
|
|
77
|
-
});
|
|
76
|
+
foot: actions(copyButton(() => text, "Copy result", "fill"))});
|
|
78
77
|
slot.append(card.node);
|
|
79
78
|
run();
|
|
80
79
|
|
|
@@ -4,8 +4,7 @@ import { el, bench, stack, panel, numberInput, selectInput, notice, copyButton,
|
|
|
4
4
|
const SCALES = [
|
|
5
5
|
{ value: "c", label: "Celsius" },
|
|
6
6
|
{ value: "f", label: "Fahrenheit" },
|
|
7
|
-
{ value: "k", label: "Kelvin" }
|
|
8
|
-
];
|
|
7
|
+
{ value: "k", label: "Kelvin" }];
|
|
9
8
|
|
|
10
9
|
const byId = Object.fromEntries(SCALES.map((scale) => [scale.value, scale]));
|
|
11
10
|
const options = SCALES.map(({ value, label }) => ({ value, label }));
|
|
@@ -19,8 +18,7 @@ const FORMULAS = {
|
|
|
19
18
|
"f-c": "C = (F - 32) x 5/9",
|
|
20
19
|
"f-k": "K = (F - 32) x 5/9 + 273.15",
|
|
21
20
|
"k-c": "C = K - 273.15",
|
|
22
|
-
"k-f": "F = (K - 273.15) x 9/5 + 32"
|
|
23
|
-
};
|
|
21
|
+
"k-f": "F = (K - 273.15) x 9/5 + 32"};
|
|
24
22
|
|
|
25
23
|
function fmt(value) {
|
|
26
24
|
if (!Number.isFinite(value)) return "-";
|
|
@@ -34,11 +32,11 @@ export function mount(slot) {
|
|
|
34
32
|
const value = numberInput({ value: "0", step: "any", "aria-label": "Value" });
|
|
35
33
|
const from = selectInput(options, { value: "c", "aria-label": "From scale" });
|
|
36
34
|
const to = selectInput(options, { value: "f", "aria-label": "To scale" });
|
|
37
|
-
const result = el("span", { class: "
|
|
38
|
-
const stats = el("div", { class: "
|
|
39
|
-
const formula = el("p", { class: "
|
|
35
|
+
const result = el("span", { class: "wrap-anywhere text-xl font-bold tracking-[-0.02em] tabular-nums" }, "-");
|
|
36
|
+
const stats = el("div", { class: "grid gap-2.5 [grid-template-columns:repeat(auto-fit,minmax(120px,1fr))]" }, el("div", { class: "grid gap-0.5 rounded-r1 border border-orbit bg-deck-2 px-3.5 py-3" }, el("span", { class: "text-2xs font-semibold text-ink-3" }, "Result"), result));
|
|
37
|
+
const formula = el("p", { class: "border-s-[3px] border-transparent ps-2 text-sm text-ink-3" });
|
|
40
38
|
const error = notice("error");
|
|
41
|
-
const scroll = el("div", { class: "
|
|
39
|
+
const scroll = el("div", { class: "max-h-80 overflow-auto" });
|
|
42
40
|
let text = "";
|
|
43
41
|
|
|
44
42
|
function run() {
|
|
@@ -59,7 +57,7 @@ export function mount(slot) {
|
|
|
59
57
|
const out = fromCelsius(celsius, b.value);
|
|
60
58
|
result.textContent = `${fmt(out)} ${b.value === "k" ? "K" : `°${b.value.toUpperCase()}`}`;
|
|
61
59
|
formula.textContent = a.value === b.value ? "" : FORMULAS[`${a.value}-${b.value}`];
|
|
62
|
-
scroll.replaceChildren(el("table", { class: "
|
|
60
|
+
scroll.replaceChildren(el("table", { class: "w-full border-collapse font-mono text-sm [&_th]:wrap-anywhere [&_td]:wrap-anywhere [&_th]:border-b [&_td]:border-b [&_th]:border-orbit [&_td]:border-orbit [&_th]:px-2.5 [&_td]:px-2.5 [&_th]:py-2 [&_td]:py-2 [&_th]:text-start [&_td]:text-start [&_th]:font-sans [&_th]:text-2xs [&_th]:font-semibold [&_th]:text-ink-2 [&_tbody>tr:last-child>th]:border-b-0 [&_tbody>tr:last-child>td]:border-b-0" },
|
|
63
61
|
el("thead", {}, el("tr", {}, el("th", {}, "Scale"), el("th", {}, "Value"))),
|
|
64
62
|
el("tbody", {}, ...SCALES.map((scale) => el("tr", {},
|
|
65
63
|
el("td", {}, scale.label),
|
|
@@ -83,8 +81,7 @@ export function mount(slot) {
|
|
|
83
81
|
stack(panel("Value", value), panel("From", from), panel("To", to), error),
|
|
84
82
|
stack(panel("Result", stats), formula, panel("All scales", scroll))
|
|
85
83
|
),
|
|
86
|
-
foot: actions(swap, copyButton(() => text, "Copy result", "fill"))
|
|
87
|
-
});
|
|
84
|
+
foot: actions(swap, copyButton(() => text, "Copy result", "fill"))});
|
|
88
85
|
slot.append(card.node);
|
|
89
86
|
run();
|
|
90
87
|
|
|
@@ -8,9 +8,8 @@ export function mount(slot) {
|
|
|
8
8
|
const right = textArea({ rows: 9, "aria-label": "Second text", placeholder: "Second text" });
|
|
9
9
|
const options = toggleRow([
|
|
10
10
|
{ value: "ignore", label: "Ignore case", checked: false },
|
|
11
|
-
{ value: "spaces", label: "Ignore spaces", checked: false }
|
|
12
|
-
|
|
13
|
-
const diffBox = el("div", { class: "mvb-diff" });
|
|
11
|
+
{ value: "spaces", label: "Ignore spaces", checked: false }]);
|
|
12
|
+
const diffBox = el("div", { class: "grid gap-px overflow-hidden rounded-r1 border border-orbit bg-orbit" });
|
|
14
13
|
const note = notice("note");
|
|
15
14
|
let rows = [];
|
|
16
15
|
|
|
@@ -66,8 +65,14 @@ export function mount(slot) {
|
|
|
66
65
|
const removed = rows.filter(([type]) => type === "del").length;
|
|
67
66
|
diffBox.replaceChildren(...rows.map(([type, raw]) => el(
|
|
68
67
|
"div",
|
|
69
|
-
{
|
|
70
|
-
|
|
68
|
+
{
|
|
69
|
+
// One row skin. The add/del tint and the sign colour hang off the data
|
|
70
|
+
// attribute, so a row never carries two competing background utilities.
|
|
71
|
+
class:
|
|
72
|
+
"grid bg-deck font-mono text-2xs [grid-template-columns:34px_minmax(0,1fr)] [&>span]:wrap-anywhere [&>span]:px-2.5 [&>span]:py-[3px] [&>span]:whitespace-pre-wrap data-[kind=add]:bg-[var(--add)] data-[kind=add]:[&>span:first-child]:text-jade data-[kind=del]:bg-[var(--del)] data-[kind=del]:[&>span:first-child]:text-danger",
|
|
73
|
+
dataset: { kind: type },
|
|
74
|
+
},
|
|
75
|
+
el("span", { class: "border-e border-orbit text-center text-ink-3 select-none" }, type === "add" ? "+" : type === "del" ? "-" : " "),
|
|
71
76
|
el("span", {}, raw)
|
|
72
77
|
)));
|
|
73
78
|
const counts = !added && !removed
|
|
@@ -93,8 +98,7 @@ export function mount(slot) {
|
|
|
93
98
|
stack(panel("First text", left), panel("Second text", right), options, note),
|
|
94
99
|
stack(panel("Difference", diffBox))
|
|
95
100
|
),
|
|
96
|
-
foot: actions(swapButton, copyButton(diffText, "Copy diff", "fill"))
|
|
97
|
-
});
|
|
101
|
+
foot: actions(swapButton, copyButton(diffText, "Copy diff", "fill"))});
|
|
98
102
|
slot.append(card.node);
|
|
99
103
|
run();
|
|
100
104
|
|
|
@@ -14,7 +14,7 @@ function detect(text) {
|
|
|
14
14
|
|
|
15
15
|
export function mount(slot) {
|
|
16
16
|
const source = textArea({ rows: 6, "aria-label": "Text", placeholder: "Type or paste text" });
|
|
17
|
-
const sample = el("div", { class: "
|
|
17
|
+
const sample = el("div", { class: "w-full rounded-lg border border-orbit bg-deck px-[18px] py-4" }, "Sample text");
|
|
18
18
|
const code = output();
|
|
19
19
|
|
|
20
20
|
let mode = "auto";
|
|
@@ -22,8 +22,7 @@ export function mount(slot) {
|
|
|
22
22
|
[
|
|
23
23
|
{ value: "auto", label: "Auto" },
|
|
24
24
|
{ value: "ltr", label: "Left to right" },
|
|
25
|
-
{ value: "rtl", label: "Right to left" },
|
|
26
|
-
],
|
|
25
|
+
{ value: "rtl", label: "Right to left" }],
|
|
27
26
|
{ value: "auto", label: "Direction", onChange: (next) => { mode = next; run(); } }
|
|
28
27
|
);
|
|
29
28
|
|
|
@@ -44,8 +43,7 @@ export function mount(slot) {
|
|
|
44
43
|
stack(panel("Text", source), panel("Direction", dirChips)),
|
|
45
44
|
stack(panel("Preview", sample), panel("Attribute", code))
|
|
46
45
|
),
|
|
47
|
-
foot: actions(copyButton(() => snippet, "Copy attribute", "fill"))
|
|
48
|
-
});
|
|
46
|
+
foot: actions(copyButton(() => snippet, "Copy attribute", "fill"))});
|
|
49
47
|
slot.append(card.node);
|
|
50
48
|
run();
|
|
51
49
|
|