Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 18 additions & 17 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -144,23 +144,24 @@ Feel free to [open a PR](https://github.com/DenverCoder1/readme-typing-svg/issue

## 🔧 Options

| Parameter | Details | Type | Example |
| :-------------: | :-------------------------------------------------------------------------: | :-----: | :---------------------------------------------------------------------------------------------------------------: |
| `lines` | Text to display with lines separated by `;` and `+` for spaces | string | `First+line;Second+line;Third+line` |
| `height` | Height of the output SVG in pixels (default: `50`) | integer | Any positive number |
| `width` | Width of the output SVG in pixels (default: `400`) | integer | Any positive number |
| `size` | Font size in pixels (default: `20`) | integer | Any positive number |
| `font` | Font family (default: `monospace`) | string | Any font from Google Fonts |
| `color` | Color of the text (default: `36BCF7`) | string | Hex code without # (eg. `F724A9`) |
| `background` | Background color of the text (default: `00000000`) | string | Hex code without # (eg. `FEFF4C`) |
| `center` | `true` to center text or `false` for left aligned (default: `false`) | boolean | `true` or `false` |
| `vCenter` | `true` to center vertically or `false`(default) to align above the center | boolean | `true` or `false` |
| `multiline` | `true` to wrap lines or `false` to retype on one line (default: `false`) | boolean | `true` or `false` |
| `duration` | Duration of the printing of a single line in milliseconds (default: `5000`) | integer | Any positive number |
| `pause` | Duration of the pause between lines in milliseconds (default: `0`) | integer | Any non-negative number |
| `repeat` | `true` to loop around to the first line after the last (default: `true`) | boolean | `true` or `false` |
| `separator` | Separator used between lines in the lines parameter (default: `;`) | string | `;`, `;;`, `/`, etc. |
| `letterSpacing` | Letter spacing (default: `normal`) | string | Any css values for the [letter-spacing](https://developer.mozilla.org/en-US/docs/Web/CSS/letter-spacing) property |
| Parameter | Details | Type | Example |
| :-------------: | :---------------------------------------------------------------------------------: | :-----: | :---------------------------------------------------------------------------------------------------------------: |
| `lines` | Text to display with lines separated by `;` and `+` for spaces | string | `First+line;Second+line;Third+line` |
| `height` | Height of the output SVG in pixels (default: `50`) | integer | Any positive number |
| `width` | Width of the output SVG in pixels (default: `400`) | integer | Any positive number |
| `size` | Font size in pixels (default: `20`) | integer | Any positive number |
| `font` | Font family (default: `monospace`) | string | Any font from Google Fonts |
| `color` | Color of the text (default: `36BCF7`) | string | Hex code without # (eg. `F724A9`) |
| `background` | Background color of the text (default: `00000000`) | string | Hex code without # (eg. `FEFF4C`) |
| `center` | `true` to center text or `false` for left aligned (default: `false`) | boolean | `true` or `false` |
| `vCenter` | `true` to center vertically or `false`(default) to align above the center | boolean | `true` or `false` |
| `multiline` | `true` to wrap lines or `false` to retype on one line (default: `false`) | boolean | `true` or `false` |
| `duration` | Duration of the printing of a single line in milliseconds (default: `5000`) | integer | Any positive number |
| `pause` | Duration of the pause between lines in milliseconds (default: `0`) | integer | Any non-negative number |
| `repeat` | `true` to loop around to the first line after the last (default: `true`) | boolean | `true` or `false` |
| `groups` | Group lines so each group types sequentially then clears together. Sizes must sum to the number of non-empty lines (default: `none`) | string | `2,1` (first 2 lines as group 1, next 1 as group 2) |
| `separator` | Separator used between lines in the lines parameter (default: `;`) | string | `;`, `;;`, `/`, etc. |
| `letterSpacing` | Letter spacing (default: `normal`) | string | Any css values for the [letter-spacing](https://developer.mozilla.org/en-US/docs/Web/CSS/letter-spacing) property |

## 📤 Deploying it on your own

Expand Down
85 changes: 81 additions & 4 deletions src/demo/css/style.css
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,9 @@ h2 {
border-radius: 6px;
cursor: pointer;
font-family: inherit;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.12), 0 1px 2px rgba(0, 0, 0, 0.24);
box-shadow:
0 1px 3px rgba(0, 0, 0, 0.12),
0 1px 2px rgba(0, 0, 0, 0.24);
transition: 0.2s ease-in-out;
}

Expand All @@ -111,7 +113,9 @@ h2 {

.btn:not(:disabled):hover {
background-color: var(--blue-dark);
box-shadow: 0 3px 6px rgba(0, 0, 0, 0.16), 0 3px 6px rgba(0, 0, 0, 0.23);
box-shadow:
0 3px 6px rgba(0, 0, 0, 0.16),
0 3px 6px rgba(0, 0, 0, 0.23);
}

.btn:disabled {
Expand Down Expand Up @@ -196,7 +200,9 @@ input:invalid {

input:focus:invalid {
outline: none;
box-shadow: 0 0 0 1px var(--blue-light), 0 0 0 3px var(--red);
box-shadow:
0 0 0 1px var(--blue-light),
0 0 0 3px var(--red);
}

.delete-line.btn {
Expand All @@ -213,14 +219,85 @@ input:focus:invalid {
}

.delete-line.btn:not(:disabled):hover {
box-shadow: 0 1px 3px rgb(0 0 0 / 12%), 0 1px 2px rgb(0 0 0 / 24%);
box-shadow:
0 1px 3px rgb(0 0 0 / 12%),
0 1px 2px rgb(0 0 0 / 24%);
background: #e4535314;
}

.delete-line.btn:disabled {
color: var(--stroke);
}

.group-divider {
grid-column: 1 / -1;
/* display:none gives a true zero grid footprint (no row, no surrounding gap);
opacity/height would still reserve space and leave large gaps between lines. */
display: none;
align-items: center;
gap: 6px;
height: 18px;
cursor: pointer;
user-select: none;
opacity: 0.4;
/* Reset: divider is a real <button> so it is keyboard focusable/activatable */
width: 100%;
background: none;
border: none;
padding: 0;
font: inherit;
transition:
opacity 0.15s,
background 0.15s;
}

/* focus-within reveals the dividers before Tab order reaches them, so keyboard users can land on the buttons to create groups. */
.lines:hover .group-divider,
.lines:focus-within .group-divider,
.group-divider.active {
display: flex;
}

.group-divider:hover,
.group-divider:focus-visible {
opacity: 0.7;
}

.group-divider.active {
opacity: 1;
}

.group-divider::before,
.group-divider::after {
content: "";
flex: 1;
height: 1px;
background: var(--stroke);
transition:
background 0.15s,
height 0.15s;
}

.group-divider.active::before,
.group-divider.active::after {
height: 2px;
background: var(--blue-light);
}

.group-divider-label {
font-size: 9px;
text-transform: uppercase;
letter-spacing: 0.5px;
white-space: nowrap;
color: var(--stroke);
transition: color 0.15s;
}

.group-divider.active .group-divider-label {
color: var(--blue-light);
font-weight: 600;
}

.add-line.btn {
width: 100px;
font-size: 1em;
Expand Down
95 changes: 90 additions & 5 deletions src/demo/js/script.js
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,20 @@ const preview = {
}
}
params.lines = mergeLines(lineInputs, params.separator);
// backend validates sum(groups) == line count, so skip empty inputs
const activeDividers = Array.from(document.querySelectorAll(".group-divider.active"));
if (activeDividers.length > 0) {
const breaks = activeDividers.map((d) => Number(d.dataset.dividerAfter)).sort((a, b) => a - b);
const segments = [0, ...breaks, lineInputs.length];
const groups = [];
for (let i = 0; i < segments.length - 1; i++) {
const count = lineInputs.slice(segments[i], segments[i + 1]).filter((el) => el.value.length > 0).length;
if (count > 0) groups.push(count);
}
if (groups.length > 1) {
params.groups = groups.join(",");
}
}
return params;
},

Expand Down Expand Up @@ -127,6 +141,28 @@ const preview = {
return encodeURIComponent(str).replace(/%3B/g, ";").replace(/%20/g, "+");
},

/**
* Create a clickable group divider element
* @param {number} afterIndex The line index this divider sits after
* @returns {HTMLElement} The divider element
*/
createDivider(afterIndex) {
// Button element so the divider is keyboard focusable and activatable; type=button stops it submitting the form.
const divider = document.createElement("button");
divider.type = "button";
divider.className = "group-divider";
divider.dataset.dividerAfter = afterIndex;
divider.title = "Click to split into separate groups";
divider.setAttribute("aria-label", "Toggle group break");
divider.setAttribute("aria-pressed", "false");
divider.innerHTML = '<span class="group-divider-label">new group</span>';
divider.addEventListener("click", function () {
const active = this.classList.toggle("active");
this.setAttribute("aria-pressed", String(active));
});
Comment on lines +159 to +162
return divider;
},

/**
* Add new line input fields
* @param {number} count The number of lines to add
Expand All @@ -136,6 +172,9 @@ const preview = {
for (let i = 0; i < count; i++) {
const parent = document.querySelector(".lines");
const index = parent.querySelectorAll("input").length + 1;
if (index > 1) {
parent.appendChild(this.createDivider(index - 1));
}
// label
const label = document.createElement("label");
label.innerText = `Line ${index}`;
Expand Down Expand Up @@ -180,10 +219,34 @@ const preview = {
removeLine(index) {
index = Number(index);
const parent = document.querySelector(".lines");
// A line has a divider on each side; if either was an active group boundary the
// boundary must survive when the two neighbouring lines merge into one gap.
const prevDivider = parent.querySelector(`.group-divider[data-divider-after="${index - 1}"]`);
const nextDivider = parent.querySelector(`.group-divider[data-divider-after="${index}"]`);
const boundarySurvives =
(prevDivider && prevDivider.classList.contains("active")) ||
(nextDivider && nextDivider.classList.contains("active"));
// remove all elements for given property
parent.querySelectorAll(`[data-index="${index}"]`).forEach((el) => {
parent.removeChild(el);
});
// Remove the divider before the deleted line (index-1); line 1 has no preceding
// divider, so remove the one after it instead.
const dividerToRemove = index > 1 ? index - 1 : 1;
const divider = parent.querySelector(`.group-divider[data-divider-after="${dividerToRemove}"]`);
if (divider) parent.removeChild(divider);
// divider positions shift down when a line is removed
parent.querySelectorAll(".group-divider").forEach((div) => {
const afterIndex = Number(div.dataset.dividerAfter);
if (afterIndex >= index) {
div.dataset.dividerAfter = afterIndex - 1;
}
});
// nextDivider has shifted into the merged gap; re-activate it if a boundary was lost.
if (index > 1 && nextDivider && boundarySurvives) {
nextDivider.classList.add("active");
nextDivider.setAttribute("aria-pressed", "true");
}
// update index numbers
const labels = parent.querySelectorAll("label");
labels.forEach((label) => {
Expand Down Expand Up @@ -225,15 +288,19 @@ const preview = {
// reset all inputs
const inputs = document.querySelectorAll(".param");
inputs.forEach((input) => {
let value = this.overrides[input.name] || this.defaults[input.name];
if (value) {
let value = this.overrides[input.name] ?? this.defaults[input.name];
if (value !== undefined) {
if (["color", "background"].includes(input.name)) {
input.jscolor.fromString(value);
} else {
input.value = value;
}
}
});
document.querySelectorAll(".group-divider.active").forEach((d) => {
d.classList.remove("active");
d.setAttribute("aria-pressed", "false");
});
},

/**
Expand Down Expand Up @@ -267,12 +334,16 @@ const preview = {
restore() {
// get parameters from URL
const urlParams = new URLSearchParams(window.location.search);
const params = { ...this.defaults, ...this.overrides, ...Object.fromEntries(urlParams) };
const params = {
...this.defaults,
...this.overrides,
...Object.fromEntries(urlParams),
};
// set all parameters
const inputs = document.querySelectorAll(".param");
inputs.forEach((input) => {
let value = params[input.name];
if (value) {
if (value !== undefined) {
if (["color", "background"].includes(input.name)) {
input.jscolor.fromString(value);
} else {
Expand All @@ -287,6 +358,20 @@ const preview = {
lineInputs.forEach((line, index) => {
document.querySelector(`#line-${index + 1}`).value = line;
});
// groups param stores sizes, need to convert to cumulative positions for divider activation
const groupsParam = urlParams.get("groups");
if (groupsParam) {
const sizes = groupsParam.split(",").map(Number);
let pos = 0;
for (let i = 0; i < sizes.length - 1; i++) {
pos += sizes[i];
const divider = document.querySelector(`.group-divider[data-divider-after="${pos}"]`);
if (divider) {
divider.classList.add("active");
divider.setAttribute("aria-pressed", "true");
}
}
}
},
};

Expand Down Expand Up @@ -348,5 +433,5 @@ window.addEventListener(
preview.restore(); // restore parameters
preview.update(); // update preview
},
false
false,
);
Loading
Loading