html-to-ascii 0.1.1 → 0.2.2

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.
@@ -0,0 +1,3 @@
1
+ export declare const ASCII: ({ children }: {
2
+ children: React.ReactNode;
3
+ }) => import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,2 @@
1
+ import { GridData } from '../types/GridData';
2
+ export declare const GridContext: import('react').Context<GridData>;
@@ -0,0 +1 @@
1
+ export declare const useGridContext: () => import('../types/GridData').GridData;
@@ -0,0 +1 @@
1
+ export declare function useReveal(grid: string[], speed?: number): string[];
@@ -0,0 +1,4 @@
1
+ export declare function useWindowDimensions(): {
2
+ width: number;
3
+ height: number;
4
+ };
@@ -0,0 +1,283 @@
1
+ import { jsxs as H, jsx as f } from "react/jsx-runtime";
2
+ import { createContext as W, useContext as k, useState as b, useEffect as u, useRef as x, useLayoutEffect as v } from "react";
3
+ const m = {
4
+ t: "─",
5
+ ti: "┴",
6
+ b: "─",
7
+ bi: "┬",
8
+ l: "│",
9
+ li: "┤",
10
+ r: "│",
11
+ ri: "├",
12
+ tl: "┌",
13
+ tr: "┐",
14
+ br: "┘",
15
+ bl: "└",
16
+ i: "┼",
17
+ fill: " "
18
+ }, w = W({
19
+ fontHeight: 0,
20
+ courierRatio: 0,
21
+ fontWidth: 0,
22
+ truncWidth: 0,
23
+ truncHeight: 0,
24
+ windowWidth: 0,
25
+ windowHeight: 0,
26
+ rows: 0,
27
+ cols: 0,
28
+ grid: [],
29
+ options: m
30
+ }), C = () => k(w);
31
+ function d(s, t = 1) {
32
+ const [n, i] = b(0);
33
+ return u(() => {
34
+ let c;
35
+ const a = () => {
36
+ i((o) => o >= s.length ? o : o + t), c = requestAnimationFrame(a);
37
+ };
38
+ return a(), () => cancelAnimationFrame(c);
39
+ }, [s, t]), s.slice(0, n);
40
+ }
41
+ const r = (s, t, n) => {
42
+ const i = s / n.fontWidth | 0;
43
+ return (t / n.fontHeight | 0) * n.cols + i;
44
+ }, y = ({ rect: s, grid: t }) => {
45
+ const n = Math.floor(s.rect.left / t.fontWidth) * t.fontWidth, i = Math.floor(s.rect.right / t.fontWidth) * t.fontWidth, c = Math.floor(s.rect.top / t.fontHeight) * t.fontHeight, a = Math.floor(s.rect.bottom / t.fontHeight) * t.fontHeight;
46
+ if (s.classList.contains("ascii-border") && !["ascii-border-l", "ascii-border-r", "ascii-border-t", "ascii-border-b"].some(
47
+ (o) => s.classList.contains(o)
48
+ ) || s.classList.contains("ascii-border-l"))
49
+ for (let o = c + t.fontHeight; o < a; o += t.fontHeight) {
50
+ const e = r(n, o, t);
51
+ switch (t.grid[e]) {
52
+ case t.options.t:
53
+ case t.options.b:
54
+ case t.options.tr:
55
+ case t.options.br:
56
+ t.grid[e] = t.options.li;
57
+ break;
58
+ default:
59
+ t.grid[e] = t.options.l;
60
+ }
61
+ }
62
+ if (s.classList.contains("ascii-border") && !["ascii-border-l", "ascii-border-r", "ascii-border-t", "ascii-border-b"].some(
63
+ (o) => s.classList.contains(o)
64
+ ) || s.classList.contains("ascii-border-r"))
65
+ for (let o = c + t.fontHeight; o < a; o += t.fontHeight) {
66
+ const e = r(i, o, t);
67
+ switch (t.grid[e]) {
68
+ case t.options.t:
69
+ case t.options.b:
70
+ case t.options.tl:
71
+ case t.options.bl:
72
+ t.grid[e] = t.options.ri;
73
+ break;
74
+ default:
75
+ t.grid[e] = t.options.r;
76
+ }
77
+ }
78
+ if (s.classList.contains("ascii-border") && !["ascii-border-l", "ascii-border-r", "ascii-border-t", "ascii-border-b"].some(
79
+ (o) => s.classList.contains(o)
80
+ ) || s.classList.contains("ascii-border-t"))
81
+ for (let o = n + t.fontWidth; o < i; o += t.fontWidth) {
82
+ const e = r(o, c, t);
83
+ switch (t.grid[e]) {
84
+ case t.options.l:
85
+ case t.options.r:
86
+ case t.options.bl:
87
+ case t.options.br:
88
+ t.grid[e] = t.options.ti;
89
+ break;
90
+ default:
91
+ t.grid[e] = t.options.t;
92
+ }
93
+ }
94
+ if (s.classList.contains("ascii-border") && !["ascii-border-l", "ascii-border-r", "ascii-border-t", "ascii-border-b"].some(
95
+ (o) => s.classList.contains(o)
96
+ ) || s.classList.contains("ascii-border-b"))
97
+ for (let o = n + t.fontWidth; o < i; o += t.fontWidth) {
98
+ const e = r(o, a, t);
99
+ switch (t.grid[e]) {
100
+ case t.options.l:
101
+ case t.options.r:
102
+ case t.options.tl:
103
+ case t.options.tr:
104
+ t.grid[e] = t.options.bi;
105
+ break;
106
+ default:
107
+ t.grid[e] = t.options.b;
108
+ }
109
+ }
110
+ if (["ascii-border", "ascii-border-tl"].some((o) => s.classList.contains(o)) || ["ascii-border-l", "ascii-border-t"].every((o) => s.classList.contains(o))) {
111
+ const o = r(n, c, t);
112
+ switch (t.grid[o]) {
113
+ case t.options.t:
114
+ case t.options.b:
115
+ case t.options.tr:
116
+ t.grid[o] = t.options.bi;
117
+ break;
118
+ case t.options.l:
119
+ case t.options.r:
120
+ case t.options.bl:
121
+ t.grid[o] = t.options.ri;
122
+ break;
123
+ case t.options.br:
124
+ t.grid[o] = t.options.i;
125
+ break;
126
+ default:
127
+ t.grid[o] = t.options.tl;
128
+ }
129
+ }
130
+ if (["ascii-border", "ascii-border-tr"].some((o) => s.classList.contains(o)) || ["ascii-border-r", "ascii-border-t"].every((o) => s.classList.contains(o))) {
131
+ const o = r(i, c, t);
132
+ switch (t.grid[o]) {
133
+ case t.options.t:
134
+ case t.options.b:
135
+ case t.options.tl:
136
+ t.grid[o] = t.options.bi;
137
+ break;
138
+ case t.options.l:
139
+ case t.options.r:
140
+ case t.options.br:
141
+ t.grid[o] = t.options.li;
142
+ break;
143
+ case t.options.bl:
144
+ t.grid[o] = t.options.i;
145
+ break;
146
+ default:
147
+ t.grid[o] = t.options.tr;
148
+ }
149
+ }
150
+ if (["ascii-border", "ascii-border-br"].some((o) => s.classList.contains(o)) || ["ascii-border-r", "ascii-border-b"].every((o) => s.classList.contains(o))) {
151
+ const o = r(i, a, t);
152
+ if (s.type === "textarea")
153
+ t.grid[o] = "▼";
154
+ else
155
+ switch (t.grid[o]) {
156
+ case t.options.l:
157
+ case t.options.r:
158
+ case t.options.tr:
159
+ t.grid[o] = t.options.li;
160
+ break;
161
+ case t.options.t:
162
+ case t.options.b:
163
+ case t.options.bl:
164
+ t.grid[o] = t.options.ti;
165
+ break;
166
+ case t.options.tl:
167
+ t.grid[o] = t.options.i;
168
+ break;
169
+ default:
170
+ t.grid[o] = t.options.br;
171
+ }
172
+ }
173
+ if (["ascii-border", "ascii-border-bl"].some((o) => s.classList.contains(o)) || ["ascii-border-l", "ascii-border-b"].every((o) => s.classList.contains(o))) {
174
+ const o = r(n, a, t);
175
+ switch (t.grid[o]) {
176
+ case t.options.l:
177
+ case t.options.r:
178
+ case t.options.tl:
179
+ t.grid[o] = t.options.ri;
180
+ break;
181
+ case t.options.t:
182
+ case t.options.b:
183
+ case t.options.br:
184
+ t.grid[o] = t.options.ti;
185
+ break;
186
+ case t.options.tr:
187
+ t.grid[o] = t.options.i;
188
+ break;
189
+ default:
190
+ t.grid[o] = t.options.bl;
191
+ }
192
+ }
193
+ if (s.classList.contains("ascii-fill"))
194
+ for (let o = c + t.fontHeight; o < a; o += t.fontHeight)
195
+ for (let e = n + t.fontWidth; e < i; e += t.fontWidth)
196
+ t.grid[r(e, o, t)] = t.options.fill;
197
+ s.classList.contains("ascii-text") && s.characters.forEach((o) => {
198
+ const e = Math.floor(o.rect.left / t.fontWidth) * t.fontWidth, l = Math.floor(o.rect.bottom / t.fontHeight) * t.fontHeight;
199
+ t.grid[r(e, l, t)] = o.char;
200
+ });
201
+ };
202
+ function R(s) {
203
+ return s.current ? Array.from(s.current.querySelectorAll('[class*="ascii"]')).map((t) => {
204
+ const n = [], i = document.createTreeWalker(t, NodeFilter.SHOW_TEXT);
205
+ for (; i.nextNode(); ) {
206
+ const c = i.currentNode, a = c.textContent ?? "";
207
+ for (let o = 0; o < a.length; o++) {
208
+ if (a[o].trim() === "") continue;
209
+ const e = document.createRange();
210
+ e.setStart(c, o), e.setEnd(c, o + 1);
211
+ const l = e.getBoundingClientRect();
212
+ n.push({ char: a[o], rect: l });
213
+ }
214
+ }
215
+ return {
216
+ rect: t.getBoundingClientRect(),
217
+ characters: n,
218
+ type: t.tagName.toLowerCase(),
219
+ classList: t.classList
220
+ };
221
+ }) : [];
222
+ }
223
+ const I = ({ children: s }) => {
224
+ const t = x(null), n = C(), [i, c] = b([]), a = d(n.grid, 30);
225
+ v(() => {
226
+ if (!t.current) return;
227
+ let o;
228
+ const e = () => {
229
+ c(R(t)), o = requestAnimationFrame(e);
230
+ };
231
+ return e(), () => cancelAnimationFrame(o);
232
+ }, []);
233
+ for (let o = 0; o < n.grid.length; o++)
234
+ n.grid[o] = " ";
235
+ return i?.forEach((o) => {
236
+ y({ rect: o, grid: n });
237
+ }), /* @__PURE__ */ H("div", { ref: t, children: [
238
+ /* @__PURE__ */ f(
239
+ "div",
240
+ {
241
+ style: { width: n.truncWidth, height: n.truncHeight },
242
+ className: "absolute opacity-0 top-0 left-0 bg-none pointer-events-none",
243
+ children: s
244
+ }
245
+ ),
246
+ t.current && /* @__PURE__ */ f("div", { style: { width: n.truncWidth, height: n.truncHeight }, className: "leading-none wrap-break-word", children: a.join("") })
247
+ ] });
248
+ };
249
+ function A() {
250
+ const [s, t] = b({
251
+ width: 0,
252
+ height: 0
253
+ });
254
+ return u(() => {
255
+ const n = () => t({ width: window.innerWidth, height: window.innerHeight });
256
+ return n(), window.addEventListener("resize", n), () => window.removeEventListener("resize", n);
257
+ }, []), s;
258
+ }
259
+ function E({ width: s, height: t }) {
260
+ const i = 0.60009765625, c = 16 * i, a = s - s % c, o = t - t % 16, e = s, l = t, h = Math.floor(o / 16), p = Math.floor(a / c), L = Array.from({ length: h * p }, () => " ");
261
+ return {
262
+ fontHeight: 16,
263
+ courierRatio: i,
264
+ fontWidth: c,
265
+ truncWidth: a,
266
+ truncHeight: o,
267
+ windowWidth: e,
268
+ windowHeight: l,
269
+ rows: h,
270
+ cols: p,
271
+ grid: L,
272
+ options: m
273
+ };
274
+ }
275
+ function F({ children: s }) {
276
+ const { width: t, height: n } = A(), i = E({ width: t, height: n });
277
+ return /* @__PURE__ */ f(w.Provider, { value: i, children: s });
278
+ }
279
+ export {
280
+ I as ASCII,
281
+ F as ASCIIProvider,
282
+ C as useGridContext
283
+ };
@@ -0,0 +1,3 @@
1
+ export { ASCII } from './components/ASCII.tsx';
2
+ export { useGridContext } from './hooks/useGridContext.tsx';
3
+ export { ASCIIProvider } from './providers/ASCIIProvider.tsx';
@@ -0,0 +1,3 @@
1
+ export declare function ASCIIProvider({ children }: {
2
+ children: React.ReactNode;
3
+ }): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,2 @@
1
+ import { GridOptions } from '../types/GridOptions';
2
+ export declare const defaultOptions: GridOptions;
package/package.json CHANGED
@@ -1,11 +1,16 @@
1
1
  {
2
2
  "name": "html-to-ascii",
3
3
  "private": false,
4
- "version": "0.1.1",
4
+ "version": "0.2.2",
5
5
  "type": "module",
6
+ "main": "dist/index.js",
7
+ "types": "dist/index.d.ts",
8
+ "files": [
9
+ "dist"
10
+ ],
6
11
  "scripts": {
7
12
  "dev": "vite",
8
- "build": "tsc -b && vite build",
13
+ "build": "tsc -b ./tsconfig.lib.json && vite build",
9
14
  "lint": "eslint .",
10
15
  "preview": "vite preview"
11
16
  },
@@ -27,6 +32,7 @@
27
32
  "globals": "^16.5.0",
28
33
  "typescript": "~5.9.3",
29
34
  "typescript-eslint": "^8.46.4",
30
- "vite": "^7.2.4"
35
+ "vite": "^7.2.4",
36
+ "vite-plugin-dts": "^4.5.4"
31
37
  }
32
38
  }
package/CHANGELOG.md DELETED
@@ -1,16 +0,0 @@
1
- # Changelog
2
-
3
- All notable changes to this project will be documented in this file.
4
-
5
- The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
- and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
-
8
- ## [Unreleased]
9
-
10
- ## [0.1.0] - 2026-02-08
11
-
12
- ### Added
13
-
14
- - Basic functionality
15
- - ASCII box customization
16
- - Basic position change monitoring
package/eslint.config.js DELETED
@@ -1,23 +0,0 @@
1
- import js from '@eslint/js'
2
- import globals from 'globals'
3
- import reactHooks from 'eslint-plugin-react-hooks'
4
- import reactRefresh from 'eslint-plugin-react-refresh'
5
- import tseslint from 'typescript-eslint'
6
- import { defineConfig, globalIgnores } from 'eslint/config'
7
-
8
- export default defineConfig([
9
- globalIgnores(['dist']),
10
- {
11
- files: ['**/*.{ts,tsx}'],
12
- extends: [
13
- js.configs.recommended,
14
- tseslint.configs.recommended,
15
- reactHooks.configs.flat.recommended,
16
- reactRefresh.configs.vite,
17
- ],
18
- languageOptions: {
19
- ecmaVersion: 2020,
20
- globals: globals.browser,
21
- },
22
- },
23
- ])
package/index.html DELETED
@@ -1,12 +0,0 @@
1
- <!doctype html>
2
- <html lang="en">
3
- <head>
4
- <meta charset="UTF-8" />
5
- <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
- <title>html-to-ascii</title>
7
- </head>
8
- <body>
9
- <div id="root"></div>
10
- <script type="module" src="/src/main.tsx"></script>
11
- </body>
12
- </html>
package/src/App.css DELETED
@@ -1 +0,0 @@
1
- @import "tailwindcss";
package/src/App.tsx DELETED
@@ -1,39 +0,0 @@
1
- import ASCII from "./components/ASCII"
2
-
3
- function App() {
4
- return (
5
- <ASCII>
6
- <div className="flex justify-center">
7
- <div className="w-72 h-48 border text-center p-4 ascii-border">
8
- <div className="m-4 ascii-border-tl ascii-border-br h-8" />
9
- <div className="m-2 border h-8 ascii-border-r ascii-border-b" />
10
- <button className="ascii-border ascii-text">button</button>
11
- <br></br>
12
- <a href="https://en.wikipedia.org/wiki/Block_Elements" className="ascii-border ascii-text">
13
- a link
14
- </a>
15
- </div>
16
- <textarea className="w-64 h-64 ascii-border" defaultValue={"test"}></textarea>
17
- <br></br>
18
- <div className="p-4">
19
- {" "}
20
- <label htmlFor="cars" className="border">
21
- Choose a car:
22
- </label>
23
- <select name="cars" id="cars" className="border">
24
- <option value="volvo">Volvo</option>
25
- <option value="saab">Saab</option>
26
- <option value="mercedes">Mercedes</option>
27
- <option value="audi">Audi</option>
28
- </select>
29
- </div>
30
- </div>
31
- {/* <img
32
- src="https://external-content.duckduckgo.com/iu/?u=https%3A%2F%2Fmancelona-vet.com%2Fwp-content%2Fuploads%2F2022%2F04%2FPuppy-Care.png&f=1&nofb=1&ipt=fdd8b957d76c0c6eda753c0b89ca41de8642c16b654126bf5cd82e61c0d114ea"
33
- className="ascii-border opacity-100 p-10 mt-4"
34
- ></img> */}
35
- </ASCII>
36
- )
37
- }
38
-
39
- export default App
@@ -1,322 +0,0 @@
1
- import { useLayoutEffect, useRef, useState } from "react"
2
- import type GridData from "../types/GridData"
3
- import type Rect from "../types/Rect"
4
- import { useGridContext } from "../hooks/useGridContext"
5
-
6
- const getIndex = (x: number, y: number, grid: GridData) => {
7
- const col = (x / grid.fontWidth) | 0
8
- const row = (y / grid.fontHeight) | 0
9
- return row * grid.cols + col
10
- }
11
-
12
- const drawRect = ({ rect, grid }: { rect: Rect; grid: GridData }) => {
13
- const rectLeft = Math.floor(rect.rect.left / grid.fontWidth) * grid.fontWidth
14
- const rectRight = Math.floor(rect.rect.right / grid.fontWidth) * grid.fontWidth
15
- const rectTop = Math.floor(rect.rect.top / grid.fontHeight) * grid.fontHeight
16
- const rectBottom = Math.floor(rect.rect.bottom / grid.fontHeight) * grid.fontHeight
17
-
18
- //verticals
19
- //left
20
- if (
21
- (rect.classList.contains("ascii-border") &&
22
- !["ascii-border-l", "ascii-border-r", "ascii-border-t", "ascii-border-b"].some((c) =>
23
- rect.classList.contains(c),
24
- )) ||
25
- rect.classList.contains("ascii-border-l")
26
- ) {
27
- for (let i = rectTop + grid.fontHeight; i < rectBottom; i += grid.fontHeight) {
28
- const l = getIndex(rectLeft, i, grid)
29
-
30
- switch (grid.grid[l]) {
31
- case grid.options.t:
32
- case grid.options.b:
33
- case grid.options.tr:
34
- case grid.options.br:
35
- grid.grid[l] = grid.options.li
36
- break
37
- default:
38
- grid.grid[l] = grid.options.l
39
- }
40
- }
41
- }
42
- //right
43
- if (
44
- (rect.classList.contains("ascii-border") &&
45
- !["ascii-border-l", "ascii-border-r", "ascii-border-t", "ascii-border-b"].some((c) =>
46
- rect.classList.contains(c),
47
- )) ||
48
- rect.classList.contains("ascii-border-r")
49
- ) {
50
- for (let i = rectTop + grid.fontHeight; i < rectBottom; i += grid.fontHeight) {
51
- const r = getIndex(rectRight, i, grid)
52
-
53
- switch (grid.grid[r]) {
54
- case grid.options.t:
55
- case grid.options.b:
56
- case grid.options.tl:
57
- case grid.options.bl:
58
- grid.grid[r] = grid.options.ri
59
- break
60
- default:
61
- grid.grid[r] = grid.options.r
62
- }
63
- }
64
- }
65
-
66
- //horizontals
67
- //top
68
- if (
69
- (rect.classList.contains("ascii-border") &&
70
- !["ascii-border-l", "ascii-border-r", "ascii-border-t", "ascii-border-b"].some((c) =>
71
- rect.classList.contains(c),
72
- )) ||
73
- rect.classList.contains("ascii-border-t")
74
- ) {
75
- for (let i = rectLeft + grid.fontWidth; i < rectRight; i += grid.fontWidth) {
76
- const t = getIndex(i, rectTop, grid)
77
-
78
- switch (grid.grid[t]) {
79
- case grid.options.l:
80
- case grid.options.r:
81
- case grid.options.bl:
82
- case grid.options.br:
83
- grid.grid[t] = grid.options.ti
84
- break
85
- default:
86
- grid.grid[t] = grid.options.t
87
- }
88
- }
89
- }
90
- //bottom
91
- if (
92
- (rect.classList.contains("ascii-border") &&
93
- !["ascii-border-l", "ascii-border-r", "ascii-border-t", "ascii-border-b"].some((c) =>
94
- rect.classList.contains(c),
95
- )) ||
96
- rect.classList.contains("ascii-border-b")
97
- ) {
98
- for (let i = rectLeft + grid.fontWidth; i < rectRight; i += grid.fontWidth) {
99
- const b = getIndex(i, rectBottom, grid)
100
- switch (grid.grid[b]) {
101
- case grid.options.l:
102
- case grid.options.r:
103
- case grid.options.tl:
104
- case grid.options.tr:
105
- grid.grid[b] = grid.options.bi
106
- break
107
- default:
108
- grid.grid[b] = grid.options.b
109
- }
110
- }
111
- }
112
-
113
- //corners
114
- //tl
115
- if (
116
- ["ascii-border", "ascii-border-tl"].some((c) => rect.classList.contains(c)) ||
117
- ["ascii-border-l", "ascii-border-t"].every((c) => rect.classList.contains(c))
118
- ) {
119
- const tl = getIndex(rectLeft, rectTop, grid)
120
- switch (grid.grid[tl]) {
121
- case grid.options.t:
122
- case grid.options.b:
123
- case grid.options.tr:
124
- grid.grid[tl] = grid.options.bi
125
- break
126
- case grid.options.l:
127
- case grid.options.r:
128
- case grid.options.bl:
129
- grid.grid[tl] = grid.options.ri
130
- break
131
- case grid.options.br:
132
- grid.grid[tl] = grid.options.i
133
- break
134
- default:
135
- grid.grid[tl] = grid.options.tl
136
- }
137
- }
138
- //tr
139
- if (
140
- ["ascii-border", "ascii-border-tr"].some((c) => rect.classList.contains(c)) ||
141
- ["ascii-border-r", "ascii-border-t"].every((c) => rect.classList.contains(c))
142
- ) {
143
- const tr = getIndex(rectRight, rectTop, grid)
144
- switch (grid.grid[tr]) {
145
- case grid.options.t:
146
- case grid.options.b:
147
- case grid.options.tl:
148
- grid.grid[tr] = grid.options.bi
149
- break
150
- case grid.options.l:
151
- case grid.options.r:
152
- case grid.options.br:
153
- grid.grid[tr] = grid.options.li
154
- break
155
- case grid.options.bl:
156
- grid.grid[tr] = grid.options.i
157
- break
158
- default:
159
- grid.grid[tr] = grid.options.tr
160
- }
161
- }
162
- //br
163
- if (
164
- ["ascii-border", "ascii-border-br"].some((c) => rect.classList.contains(c)) ||
165
- ["ascii-border-r", "ascii-border-b"].every((c) => rect.classList.contains(c))
166
- ) {
167
- const br = getIndex(rectRight, rectBottom, grid)
168
- if (rect.type === "textarea") {
169
- grid.grid[br] = "▼"
170
- } else {
171
- switch (grid.grid[br]) {
172
- case grid.options.l:
173
- case grid.options.r:
174
- case grid.options.tr:
175
- grid.grid[br] = grid.options.li
176
- break
177
- case grid.options.t:
178
- case grid.options.b:
179
- case grid.options.bl:
180
- grid.grid[br] = grid.options.ti
181
- break
182
- case grid.options.tl:
183
- grid.grid[br] = grid.options.i
184
- break
185
- default:
186
- grid.grid[br] = grid.options.br
187
- }
188
- }
189
- }
190
-
191
- //bl
192
- if (
193
- ["ascii-border", "ascii-border-bl"].some((c) => rect.classList.contains(c)) ||
194
- ["ascii-border-l", "ascii-border-b"].every((c) => rect.classList.contains(c))
195
- ) {
196
- const bl = getIndex(rectLeft, rectBottom, grid)
197
- switch (grid.grid[bl]) {
198
- case grid.options.l:
199
- case grid.options.r:
200
- case grid.options.tl:
201
- grid.grid[bl] = grid.options.ri
202
- break
203
- case grid.options.t:
204
- case grid.options.b:
205
- case grid.options.br:
206
- grid.grid[bl] = grid.options.ti
207
- break
208
- case grid.options.tr:
209
- grid.grid[bl] = grid.options.i
210
- break
211
- default:
212
- grid.grid[bl] = grid.options.bl
213
- }
214
- }
215
-
216
- //fill
217
- if (rect.classList.contains("ascii-fill")) {
218
- for (let y = rectTop + grid.fontHeight; y < rectBottom; y += grid.fontHeight) {
219
- for (let x = rectLeft + grid.fontWidth; x < rectRight; x += grid.fontWidth) {
220
- grid.grid[getIndex(x, y, grid)] = grid.options.fill
221
- }
222
- }
223
- }
224
-
225
- //characters
226
- if (rect.classList.contains("ascii-text")) {
227
- rect.characters.forEach((c) => {
228
- const cRectLeft = Math.floor(c.rect.left / grid.fontWidth) * grid.fontWidth
229
- const cRectBottom = Math.floor(c.rect.bottom / grid.fontHeight) * grid.fontHeight
230
- grid.grid[getIndex(cRectLeft, cRectBottom, grid)] = c.char
231
- })
232
- }
233
- }
234
-
235
- function getElements(ref: React.RefObject<HTMLDivElement | null>): Rect[] {
236
- if (!ref.current) return []
237
- return Array.from(ref.current.querySelectorAll<HTMLElement>('[class*="ascii"]')).map((el) => {
238
- //console.log("element", el)
239
- const c: { char: string; rect: DOMRect }[] = []
240
- const textWalker = document.createTreeWalker(el, NodeFilter.SHOW_TEXT)
241
-
242
- while (textWalker.nextNode()) {
243
- const textNode = textWalker.currentNode as Text
244
- const text = textNode.textContent ?? ""
245
-
246
- for (let i = 0; i < text.length; i++) {
247
- if (text[i].trim() === "") continue
248
- //console.log(text[i])
249
- const range = document.createRange()
250
- range.setStart(textNode, i)
251
- range.setEnd(textNode, i + 1)
252
-
253
- const rect = range.getBoundingClientRect()
254
- //if (rect.width === 0 || rect.height === 0) continue
255
-
256
- c.push({ char: text[i], rect })
257
- }
258
- }
259
- return {
260
- rect: el.getBoundingClientRect(),
261
- characters: c,
262
- type: el.tagName.toLowerCase(),
263
- classList: el.classList,
264
- }
265
- })
266
- }
267
-
268
- export default function ASCII({ children }: { children: React.ReactNode }) {
269
- const parentRef = useRef<HTMLDivElement | null>(null)
270
- const grid = useGridContext()
271
- const [rects, setRects] = useState<Rect[] | null>([])
272
-
273
- useLayoutEffect(() => {
274
- if (!parentRef.current) return
275
-
276
- const update = () => {
277
- const r = getElements(parentRef)
278
- setRects(r)
279
- }
280
-
281
- const observer = new MutationObserver((mutations) => {
282
- mutations.forEach(() => {
283
- //console.log("DOM changed:", mutation.type)
284
- // Handle the DOM change here
285
- update()
286
- })
287
- })
288
-
289
- observer.observe(parentRef.current, {
290
- attributes: true,
291
- childList: true,
292
- subtree: true,
293
- characterData: true,
294
- })
295
- }, [])
296
-
297
- // clear canvas
298
- // maybe find better way
299
- for (let i = 0; i < grid.grid.length; i++) {
300
- grid.grid[i] = String.fromCharCode(160)
301
- }
302
-
303
- rects?.forEach((rect) => {
304
- drawRect({ rect, grid })
305
- })
306
-
307
- return (
308
- <div ref={parentRef}>
309
- <div
310
- style={{ width: grid.truncWidth, height: grid.truncHeight }}
311
- className="absolute opacity-0 top-0 left-0 bg-none pointer-events-none"
312
- >
313
- {children}
314
- </div>
315
- {parentRef.current && (
316
- <div style={{ width: grid.truncWidth, height: grid.truncHeight }} className="leading-none wrap-break-word">
317
- {grid.grid.join("")}
318
- </div>
319
- )}
320
- </div>
321
- )
322
- }
@@ -1,17 +0,0 @@
1
- import { createContext } from "react"
2
- import type GridData from "../types/GridData"
3
- import { defaultOptions } from "../utils/defaultOptions"
4
-
5
- export const GridContext = createContext<GridData>({
6
- fontHeight: 0,
7
- courierRatio: 0,
8
- fontWidth: 0,
9
- truncWidth: 0,
10
- truncHeight: 0,
11
- windowWidth: 0,
12
- windowHeight: 0,
13
- rows: 0,
14
- cols: 0,
15
- grid: [],
16
- options: defaultOptions,
17
- })
@@ -1,6 +0,0 @@
1
- import { useContext } from "react"
2
- import { GridContext } from "../contexts/GridContext"
3
-
4
- export const useGridContext = () => {
5
- return useContext(GridContext)
6
- }
@@ -1,17 +0,0 @@
1
- import { useEffect, useState } from "react"
2
-
3
- export default function useWindowDimensions() {
4
- const [dimensions, setDimensions] = useState({
5
- width: 0,
6
- height: 0,
7
- })
8
- useEffect(() => {
9
- const update = () => setDimensions({ width: window.innerWidth, height: window.innerHeight })
10
-
11
- update() // Set initial size
12
- window.addEventListener("resize", update)
13
- return () => window.removeEventListener("resize", update)
14
- }, [])
15
-
16
- return dimensions
17
- }
package/src/index.css DELETED
@@ -1,26 +0,0 @@
1
- @import "tailwindcss";
2
-
3
- :root {
4
- font-family: "Courier New", Courier, monospace;
5
- height: 100vh;
6
- }
7
-
8
- body {
9
- height: 100vh;
10
- }
11
-
12
- #root {
13
- height: 100vh;
14
- }
15
-
16
- a,
17
- button,
18
- input,
19
- textarea,
20
- select,
21
- area,
22
- label,
23
- details,
24
- summary {
25
- pointer-events: auto;
26
- }
package/src/main.tsx DELETED
@@ -1,13 +0,0 @@
1
- import { StrictMode } from "react"
2
- import { createRoot } from "react-dom/client"
3
- import "./index.css"
4
- import App from "./App.tsx"
5
- import ASCIIProvider from "./providers/ASCIIProvider.tsx"
6
-
7
- createRoot(document.getElementById("root")!).render(
8
- <StrictMode>
9
- <ASCIIProvider>
10
- <App />
11
- </ASCIIProvider>
12
- </StrictMode>,
13
- )
@@ -1,37 +0,0 @@
1
- import { GridContext } from "../contexts/GridContext"
2
- import useWindowDimensions from "../hooks/useWindowDimensions"
3
- import type GridData from "../types/GridData"
4
- import { defaultOptions } from "../utils/defaultOptions"
5
-
6
- function initGrid({ width, height }: { width: number; height: number }): GridData {
7
- const fontHeight = 16
8
- const courierRatio = 1229 / 2048
9
- const fontWidth = fontHeight * courierRatio
10
- const truncWidth = width - (width % fontWidth)
11
- const truncHeight = height - (height % fontHeight)
12
- const windowWidth = width
13
- const windowHeight = height
14
- const rows = Math.floor(truncHeight / fontHeight)
15
- const cols = Math.floor(truncWidth / fontWidth)
16
- const grid = Array.from({ length: rows * cols }, () => String.fromCharCode(160))
17
- const options = defaultOptions
18
- return {
19
- fontHeight,
20
- courierRatio,
21
- fontWidth,
22
- truncWidth,
23
- truncHeight,
24
- windowWidth,
25
- windowHeight,
26
- rows,
27
- cols,
28
- grid,
29
- options,
30
- }
31
- }
32
-
33
- export default function ASCIIProvider({ children }: { children: React.ReactNode }) {
34
- const { width, height } = useWindowDimensions()
35
- const grid = initGrid({ width, height })
36
- return <GridContext.Provider value={grid}>{children}</GridContext.Provider>
37
- }
@@ -1,15 +0,0 @@
1
- import type GridOptions from "./GridOptions"
2
-
3
- export default interface GridData {
4
- fontHeight: number | 0
5
- courierRatio: number
6
- fontWidth: number
7
- truncWidth: number
8
- truncHeight: number
9
- windowWidth: number
10
- windowHeight: number
11
- rows: number
12
- cols: number
13
- grid: string[]
14
- options: Required<GridOptions>
15
- }
@@ -1,16 +0,0 @@
1
- export default interface GridOptions {
2
- t: string
3
- ti: string
4
- b: string
5
- bi: string
6
- l: string
7
- li: string
8
- r: string
9
- ri: string
10
- tl: string
11
- tr: string
12
- br: string
13
- bl: string
14
- i: string
15
- fill: string
16
- }
@@ -1,6 +0,0 @@
1
- export default interface Rect {
2
- rect: DOMRect
3
- characters: { char: string; rect: DOMRect }[]
4
- type: string
5
- classList: DOMTokenList
6
- }
@@ -1,18 +0,0 @@
1
- import type GridOptions from "../types/GridOptions"
2
-
3
- export const defaultOptions: GridOptions = {
4
- t: "─",
5
- ti: "┴",
6
- b: "─",
7
- bi: "┬",
8
- l: "│",
9
- li: "┤",
10
- r: "│",
11
- ri: "├",
12
- tl: "┌",
13
- tr: "┐",
14
- br: "┘",
15
- bl: "└",
16
- i: "┼",
17
- fill: String.fromCharCode(160),
18
- }
package/tsconfig.app.json DELETED
@@ -1,28 +0,0 @@
1
- {
2
- "compilerOptions": {
3
- "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
4
- "target": "ES2022",
5
- "useDefineForClassFields": true,
6
- "lib": ["ES2022", "DOM", "DOM.Iterable"],
7
- "module": "ESNext",
8
- "types": ["vite/client"],
9
- "skipLibCheck": true,
10
-
11
- /* Bundler mode */
12
- "moduleResolution": "bundler",
13
- "allowImportingTsExtensions": true,
14
- "verbatimModuleSyntax": true,
15
- "moduleDetection": "force",
16
- "noEmit": true,
17
- "jsx": "react-jsx",
18
-
19
- /* Linting */
20
- "strict": true,
21
- "noUnusedLocals": true,
22
- "noUnusedParameters": true,
23
- "erasableSyntaxOnly": true,
24
- "noFallthroughCasesInSwitch": true,
25
- "noUncheckedSideEffectImports": true
26
- },
27
- "include": ["src"]
28
- }
package/tsconfig.json DELETED
@@ -1,7 +0,0 @@
1
- {
2
- "files": [],
3
- "references": [
4
- { "path": "./tsconfig.app.json" },
5
- { "path": "./tsconfig.node.json" }
6
- ]
7
- }
@@ -1,26 +0,0 @@
1
- {
2
- "compilerOptions": {
3
- "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
4
- "target": "ES2023",
5
- "lib": ["ES2023"],
6
- "module": "ESNext",
7
- "types": ["node"],
8
- "skipLibCheck": true,
9
-
10
- /* Bundler mode */
11
- "moduleResolution": "bundler",
12
- "allowImportingTsExtensions": true,
13
- "verbatimModuleSyntax": true,
14
- "moduleDetection": "force",
15
- "noEmit": true,
16
-
17
- /* Linting */
18
- "strict": true,
19
- "noUnusedLocals": true,
20
- "noUnusedParameters": true,
21
- "erasableSyntaxOnly": true,
22
- "noFallthroughCasesInSwitch": true,
23
- "noUncheckedSideEffectImports": true
24
- },
25
- "include": ["vite.config.ts"]
26
- }
package/vite.config.ts DELETED
@@ -1,8 +0,0 @@
1
- import { defineConfig } from "vite"
2
- import react from "@vitejs/plugin-react"
3
- import tailwindcss from "@tailwindcss/vite"
4
-
5
- // https://vite.dev/config/
6
- export default defineConfig({
7
- plugins: [react(), tailwindcss()],
8
- })