mirror of
https://github.com/bitcoinresearchkit/brk.git
synced 2026-06-08 14:11:56 -07:00
heatmaps: part 7
This commit is contained in:
@@ -32,6 +32,15 @@ export function onFirstIntersection(element, callback) {
|
||||
observer.observe(element);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} text
|
||||
*/
|
||||
export function createSpan(text) {
|
||||
const span = window.document.createElement("span");
|
||||
span.textContent = text;
|
||||
return span;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} name
|
||||
*/
|
||||
@@ -192,7 +201,6 @@ export function createLabeledInput({
|
||||
* @param {Object} args
|
||||
* @param {T} args.initialValue
|
||||
* @param {string} [args.id]
|
||||
* @param {string} [args.legend]
|
||||
* @param {readonly T[]} args.choices
|
||||
* @param {(value: T) => void} [args.onChange]
|
||||
* @param {(choice: T) => string} [args.toKey]
|
||||
@@ -201,7 +209,6 @@ export function createLabeledInput({
|
||||
*/
|
||||
export function createRadios({
|
||||
id,
|
||||
legend,
|
||||
choices,
|
||||
initialValue,
|
||||
onChange,
|
||||
@@ -209,7 +216,7 @@ export function createRadios({
|
||||
toLabel = /** @type {(choice: T) => string} */ ((c) => String(c)),
|
||||
toTitle,
|
||||
}) {
|
||||
const field = window.document.createElement("fieldset");
|
||||
const fieldset = window.document.createElement("fieldset");
|
||||
|
||||
const initialKey = toKey(initialValue);
|
||||
|
||||
@@ -218,41 +225,32 @@ export function createRadios({
|
||||
choices.find((c) => toKey(c) === key) ?? initialValue;
|
||||
|
||||
if (choices.length === 1) {
|
||||
const span = window.document.createElement("span");
|
||||
span.textContent = toLabel(choices[0]);
|
||||
field.append(span);
|
||||
fieldset.append(createSpan(toLabel(choices[0])));
|
||||
} else {
|
||||
if (legend) {
|
||||
const legendElement = window.document.createElement("legend");
|
||||
legendElement.textContent = legend;
|
||||
field.append(legendElement);
|
||||
}
|
||||
|
||||
const fieldId = id ?? "";
|
||||
const groupId = id ?? "";
|
||||
choices.forEach((choice) => {
|
||||
const choiceKey = toKey(choice);
|
||||
const choiceLabel = toLabel(choice);
|
||||
const key = toKey(choice);
|
||||
const { label } = createLabeledInput({
|
||||
inputId: `${fieldId}-${choiceKey.toLowerCase()}`,
|
||||
inputName: fieldId,
|
||||
inputValue: choiceKey,
|
||||
inputChecked: choiceKey === initialKey,
|
||||
inputId: `${groupId}-${key.toLowerCase()}`,
|
||||
inputName: groupId,
|
||||
inputValue: key,
|
||||
inputChecked: key === initialKey,
|
||||
title: toTitle?.(choice),
|
||||
type: "radio",
|
||||
});
|
||||
|
||||
const text = window.document.createTextNode(choiceLabel);
|
||||
const text = window.document.createTextNode(toLabel(choice));
|
||||
label.append(text);
|
||||
field.append(label);
|
||||
fieldset.append(label);
|
||||
});
|
||||
|
||||
field.addEventListener("change", (event) => {
|
||||
if (!(event.target instanceof HTMLInputElement)) return;
|
||||
onChange?.(fromKey(event.target.value));
|
||||
});
|
||||
fieldset.addEventListener("change", (event) => {
|
||||
if (!(event.target instanceof HTMLInputElement)) return;
|
||||
onChange?.(fromKey(event.target.value));
|
||||
});
|
||||
}
|
||||
|
||||
return field;
|
||||
return fieldset;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -291,33 +289,30 @@ export function createSelect({
|
||||
choices.find((c) => toKey(c) === key) ?? initialValue;
|
||||
|
||||
if (choices.length === 1) {
|
||||
const span = window.document.createElement("span");
|
||||
span.textContent = toLabel(choices[0]);
|
||||
return {
|
||||
element: span,
|
||||
element: createSpan(toLabel(choices[0])),
|
||||
get: () => initialValue,
|
||||
set: () => {},
|
||||
};
|
||||
}
|
||||
|
||||
const field = window.document.createElement("label");
|
||||
const element = window.document.createElement("label");
|
||||
if (label) {
|
||||
const span = window.document.createElement("span");
|
||||
span.textContent = label;
|
||||
field.append(span);
|
||||
element.append(createSpan(label));
|
||||
}
|
||||
|
||||
const select = window.document.createElement("select");
|
||||
select.id = id ?? "";
|
||||
select.name = id ?? "";
|
||||
field.append(select);
|
||||
element.append(select);
|
||||
|
||||
/** @param {T} choice */
|
||||
const createOption = (choice) => {
|
||||
const key = toKey(choice);
|
||||
const option = window.document.createElement("option");
|
||||
option.value = toKey(choice);
|
||||
option.value = key;
|
||||
option.textContent = toLabel(choice);
|
||||
if (toKey(choice) === initialKey) {
|
||||
if (key === initialKey) {
|
||||
option.selected = true;
|
||||
}
|
||||
return option;
|
||||
@@ -342,13 +337,11 @@ export function createSelect({
|
||||
if (remaining > 0) {
|
||||
const small = window.document.createElement("small");
|
||||
small.textContent = `+${remaining}`;
|
||||
field.append(small);
|
||||
const arrow = window.document.createElement("span");
|
||||
arrow.textContent = "↓";
|
||||
field.append(arrow);
|
||||
element.append(small);
|
||||
element.append(createSpan("↓"));
|
||||
}
|
||||
|
||||
field.addEventListener("click", (e) => {
|
||||
element.addEventListener("click", (e) => {
|
||||
if (e.target !== select && "showPicker" in select) {
|
||||
e.preventDefault();
|
||||
select.showPicker();
|
||||
@@ -356,7 +349,7 @@ export function createSelect({
|
||||
});
|
||||
|
||||
return {
|
||||
element: field,
|
||||
element,
|
||||
get: () => fromKey(select.value),
|
||||
set: (choice) => {
|
||||
select.value = toKey(choice);
|
||||
|
||||
+130
-160
@@ -16,134 +16,134 @@
|
||||
background: none;
|
||||
}
|
||||
|
||||
legend {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
right: 0;
|
||||
z-index: 20;
|
||||
font-size: var(--font-size-xs);
|
||||
line-height: var(--line-height-xs);
|
||||
pointer-events: none;
|
||||
|
||||
&::before,
|
||||
&::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
width: var(--main-padding);
|
||||
z-index: 1;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
&::before {
|
||||
left: 0;
|
||||
background-image: linear-gradient(
|
||||
to left,
|
||||
transparent,
|
||||
var(--background-color)
|
||||
);
|
||||
}
|
||||
|
||||
&::after {
|
||||
right: 0;
|
||||
background-image: linear-gradient(
|
||||
to right,
|
||||
transparent,
|
||||
var(--background-color)
|
||||
);
|
||||
}
|
||||
|
||||
> div {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
overflow-x: auto;
|
||||
padding: 0 var(--main-padding);
|
||||
padding-top: 0.375rem;
|
||||
|
||||
@media (pointer: coarse) {
|
||||
pointer-events: auto;
|
||||
}
|
||||
|
||||
> * {
|
||||
pointer-events: auto;
|
||||
}
|
||||
|
||||
> *:nth-child(2) {
|
||||
color: var(--gray);
|
||||
padding: 0 0.75rem;
|
||||
}
|
||||
}
|
||||
|
||||
padding: 0;
|
||||
top: 0;
|
||||
text-transform: lowercase;
|
||||
|
||||
select {
|
||||
text-transform: lowercase;
|
||||
}
|
||||
|
||||
> div {
|
||||
padding-bottom: 0.75rem;
|
||||
|
||||
small {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
> div:last-child {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
flex-shrink: 0;
|
||||
|
||||
> div {
|
||||
flex: 0;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
> label {
|
||||
> span {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
&:has(input:not(:checked)) {
|
||||
> span.main > span.name {
|
||||
text-decoration: line-through 1.5px var(--color);
|
||||
}
|
||||
|
||||
&:hover {
|
||||
* {
|
||||
color: var(--off-color);
|
||||
}
|
||||
|
||||
> span.main > span.name {
|
||||
text-decoration-color: var(--orange);
|
||||
}
|
||||
}
|
||||
|
||||
&:active {
|
||||
color: var(--orange);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
> a {
|
||||
padding-inline: 0.375rem;
|
||||
margin-inline: -0.375rem;
|
||||
margin-top: 0.1rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
> div {
|
||||
min-height: 0;
|
||||
height: 100%;
|
||||
margin-right: var(--negative-main-padding);
|
||||
margin-left: var(--negative-main-padding);
|
||||
|
||||
& > legend,
|
||||
& table > tr > td:not(:last-child) legend {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
right: 0;
|
||||
z-index: 20;
|
||||
font-size: var(--font-size-xs);
|
||||
line-height: var(--line-height-xs);
|
||||
pointer-events: none;
|
||||
padding: 0;
|
||||
top: 0;
|
||||
text-transform: lowercase;
|
||||
|
||||
&::before,
|
||||
&::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
width: var(--main-padding);
|
||||
z-index: 1;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
&::before {
|
||||
left: 0;
|
||||
background-image: linear-gradient(
|
||||
to left,
|
||||
transparent,
|
||||
var(--background-color)
|
||||
);
|
||||
}
|
||||
|
||||
&::after {
|
||||
right: 0;
|
||||
background-image: linear-gradient(
|
||||
to right,
|
||||
transparent,
|
||||
var(--background-color)
|
||||
);
|
||||
}
|
||||
|
||||
select {
|
||||
text-transform: lowercase;
|
||||
}
|
||||
|
||||
> div {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
overflow-x: auto;
|
||||
padding-inline: var(--main-padding);
|
||||
padding-block-start: 0.375rem;
|
||||
|
||||
@media (pointer: coarse) {
|
||||
pointer-events: auto;
|
||||
}
|
||||
|
||||
> * {
|
||||
pointer-events: auto;
|
||||
}
|
||||
|
||||
> *:nth-child(2) {
|
||||
color: var(--gray);
|
||||
padding-inline: 0.75rem;
|
||||
}
|
||||
|
||||
small {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
> div:last-child {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
flex-shrink: 0;
|
||||
|
||||
> div {
|
||||
flex: 0;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
> label {
|
||||
> span {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
&:has(input:not(:checked)) {
|
||||
> span.main > span.name {
|
||||
text-decoration: line-through 1.5px var(--color);
|
||||
}
|
||||
|
||||
&:hover {
|
||||
* {
|
||||
color: var(--off-color);
|
||||
}
|
||||
|
||||
> span.main > span.name {
|
||||
text-decoration-color: var(--orange);
|
||||
}
|
||||
}
|
||||
|
||||
&:active {
|
||||
color: var(--orange);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
> a {
|
||||
padding-inline: 0.375rem;
|
||||
margin-inline: -0.375rem;
|
||||
margin-top: 0.1rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
& table > tr > td:not(:last-child) legend > div {
|
||||
padding-bottom: 0.75rem;
|
||||
}
|
||||
|
||||
:is(fieldset:has(> label > input[type="radio"]), label:has(> select)) {
|
||||
display: flex;
|
||||
flex-shrink: 0;
|
||||
@@ -174,41 +174,6 @@
|
||||
|
||||
&:last-child > td {
|
||||
border-top: 1px;
|
||||
|
||||
&:nth-child(2) {
|
||||
position: relative;
|
||||
|
||||
> fieldset:has(> label > input[type="radio"]) {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
z-index: 10;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
pointer-events: auto;
|
||||
font-size: var(--font-size-xs);
|
||||
line-height: var(--line-height-xs);
|
||||
text-transform: uppercase;
|
||||
background-color: var(--background-color);
|
||||
padding-left: var(--main-padding);
|
||||
padding-right: 0.25rem;
|
||||
|
||||
&::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 100%;
|
||||
width: var(--main-padding);
|
||||
background-image: linear-gradient(
|
||||
to right,
|
||||
var(--background-color),
|
||||
transparent
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -218,11 +183,14 @@
|
||||
z-index: 50;
|
||||
display: inline-flex;
|
||||
font-size: var(--font-size-xs);
|
||||
line-height: var(--line-height-xs);
|
||||
align-items: center;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
tr:not(:last-child) > td:last-child > fieldset:has(> label > input[type="radio"]) {
|
||||
tr:not(:last-child)
|
||||
> td:last-child
|
||||
> fieldset:has(> label > input[type="radio"]) {
|
||||
top: 0;
|
||||
right: 0;
|
||||
gap: 0.375rem;
|
||||
@@ -304,10 +272,12 @@
|
||||
}
|
||||
|
||||
@keyframes chart-hint {
|
||||
0%, 100% {
|
||||
0%,
|
||||
100% {
|
||||
opacity: 0;
|
||||
}
|
||||
15%, 85% {
|
||||
15%,
|
||||
85% {
|
||||
opacity: 0.85;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,33 +38,18 @@
|
||||
}
|
||||
|
||||
fieldset {
|
||||
border: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
min-inline-size: 0;
|
||||
padding: 0;
|
||||
|
||||
&:has(> label > input[type="radio"]) {
|
||||
text-transform: lowercase;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
|
||||
> legend,
|
||||
> div {
|
||||
> label {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
label {
|
||||
padding: 0.5rem;
|
||||
margin: -0.5rem;
|
||||
}
|
||||
|
||||
> div {
|
||||
display: flex;
|
||||
gap: 1.5rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -105,6 +105,11 @@ button {
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
fieldset {
|
||||
min-inline-size: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 2rem;
|
||||
line-height: var(--line-height-xl);
|
||||
|
||||
Reference in New Issue
Block a user