├── src
├── index.ts
├── root.tsx
├── entry.ssr.tsx
├── entry.dev.tsx
└── components
│ └── wrap-balancer.tsx
├── bun.lockb
├── .prettierignore
├── .eslintignore
├── vite.config.ts
├── .gitignore
├── tsconfig.json
├── README.md
├── .eslintrc.cjs
└── package.json
/src/index.ts:
--------------------------------------------------------------------------------
1 | export { WrapBalancer } from "./components/wrap-balancer";
2 |
--------------------------------------------------------------------------------
/bun.lockb:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/harshmangalam/qwik-wrap-balancer/HEAD/bun.lockb
--------------------------------------------------------------------------------
/.prettierignore:
--------------------------------------------------------------------------------
1 | # Files Prettier should not format
2 | **/*.log
3 | **/.DS_Store
4 | *.
5 | dist
6 | node_modules
7 |
--------------------------------------------------------------------------------
/.eslintignore:
--------------------------------------------------------------------------------
1 | **/*.log
2 | **/.DS_Store
3 | *.
4 | .vscode/settings.json
5 | .history
6 | .yarn
7 | bazel-*
8 | bazel-bin
9 | bazel-out
10 | bazel-qwik
11 | bazel-testlogs
12 | dist
13 | dist-dev
14 | lib
15 | lib-types
16 | etc
17 | external
18 | node_modules
19 | temp
20 | tsc-out
21 | tsdoc-metadata.json
22 | target
23 | output
24 | rollup.config.js
25 | build
26 | .cache
27 | .vscode
28 | .rollup.cache
29 | dist
30 | tsconfig.tsbuildinfo
31 | vite.config.ts
32 |
--------------------------------------------------------------------------------
/vite.config.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig } from 'vite';
2 | import { qwikVite } from '@builder.io/qwik/optimizer';
3 |
4 | export default defineConfig(() => {
5 | return {
6 | build: {
7 | target: 'es2020',
8 | lib: {
9 | entry: './src/index.ts',
10 | formats: ['es', 'cjs'],
11 | fileName: (format) => `index.qwik.${format === 'es' ? 'mjs' : 'cjs'}`,
12 | },
13 | },
14 | plugins: [qwikVite()],
15 | };
16 | });
17 |
--------------------------------------------------------------------------------
/src/root.tsx:
--------------------------------------------------------------------------------
1 | import { WrapBalancer } from "./components/wrap-balancer";
2 |
3 | export default () => {
4 | return (
5 | <>
6 |
7 |
8 | Qwik Wrap Balancer
9 |
10 |
11 | Qwik: A JavaScript framework reimagined for the edge.
12 |
13 | Qwik: A JavaScript framework reimagined for the edge.
14 |
15 |
16 | >
17 | );
18 | };
19 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Build
2 | /dist
3 | /lib
4 | /lib-types
5 | /server
6 |
7 | # Development
8 | node_modules
9 |
10 | # Cache
11 | .cache
12 | .mf
13 | .vscode
14 | .rollup.cache
15 | tsconfig.tsbuildinfo
16 |
17 | # Logs
18 | logs
19 | *.log
20 | npm-debug.log*
21 | yarn-debug.log*
22 | yarn-error.log*
23 | pnpm-debug.log*
24 | lerna-debug.log*
25 |
26 | # Editor
27 | !.vscode/extensions.json
28 | .idea
29 | .DS_Store
30 | *.suo
31 | *.ntvs*
32 | *.njsproj
33 | *.sln
34 | *.sw?
35 |
36 | # Yarn
37 | .yarn/*
38 | !.yarn/releases
39 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "allowJs": true,
4 | "target": "ES2017",
5 | "module": "ES2020",
6 | "lib": ["es2020", "DOM"],
7 | "jsx": "react-jsx",
8 | "jsxImportSource": "@builder.io/qwik",
9 | "strict": true,
10 | "declaration": true,
11 | "declarationDir": "lib-types",
12 | "resolveJsonModule": true,
13 | "moduleResolution": "node",
14 | "esModuleInterop": true,
15 | "skipLibCheck": true,
16 | "incremental": true,
17 | "isolatedModules": true,
18 | "types": ["vite/client"]
19 | },
20 | "include": ["src"]
21 | }
22 |
--------------------------------------------------------------------------------
/src/entry.ssr.tsx:
--------------------------------------------------------------------------------
1 | /**
2 | * WHAT IS THIS FILE?
3 | *
4 | * SSR entry point, in all cases the application is render outside the browser, this
5 | * entry point will be the common one.
6 | *
7 | * - Server (express, cloudflare...)
8 | * - npm run start
9 | * - npm run preview
10 | * - npm run build
11 | *
12 | */
13 | import { renderToStream, RenderToStreamOptions } from '@builder.io/qwik/server';
14 | import { manifest } from '@qwik-client-manifest';
15 | import Root from './root';
16 |
17 | export default function (opts: RenderToStreamOptions) {
18 | return renderToStream(, {
19 | manifest,
20 | ...opts,
21 | });
22 | }
23 |
--------------------------------------------------------------------------------
/src/entry.dev.tsx:
--------------------------------------------------------------------------------
1 | /*
2 | * WHAT IS THIS FILE?
3 | *
4 | * Development entry point using only client-side modules:
5 | * - Do not use this mode in production!
6 | * - No SSR
7 | * - No portion of the application is pre-rendered on the server.
8 | * - All of the application is running eagerly in the browser.
9 | * - More code is transferred to the browser than in SSR mode.
10 | * - Optimizer/Serialization/Deserialization code is not exercised!
11 | */
12 | import { render, RenderOptions } from '@builder.io/qwik';
13 | import Root from './root';
14 |
15 | export default function (opts: RenderOptions) {
16 | return render(document, , opts);
17 | }
18 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Qwik Wrap Balancer ⚡️
2 |
3 | ---
4 |
5 | ## Install
6 |
7 | ```sh
8 | npm i qwikjs-wrap-balancer
9 | ```
10 |
11 | ```sh
12 | pnpm i qwikjs-wrap-balancer
13 | ```
14 |
15 | ```sh
16 | yarn add qwikjs-wrap-balancer
17 | ```
18 |
19 | ```sh
20 | bun install qwikjs-wrap-balancer
21 | ```
22 |
23 | ## Example
24 |
25 | ```js
26 | import { WrapBalancer } from "qwikjs-wrap-balancer";
27 | import { component$ } from "@builder.io/qwik";
28 | export default component$(() => {
29 | return (
30 |
31 | Build instantly-interactive web apps without effort.
32 |
33 | );
34 | });
35 | ```
36 |
37 |
38 |
--------------------------------------------------------------------------------
/.eslintrc.cjs:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | root: true,
3 | env: {
4 | browser: true,
5 | es2021: true,
6 | node: true,
7 | },
8 | extends: [
9 | 'eslint:recommended',
10 | 'plugin:@typescript-eslint/recommended',
11 | 'plugin:qwik/recommended',
12 | ],
13 | parser: '@typescript-eslint/parser',
14 | parserOptions: {
15 | tsconfigRootDir: __dirname,
16 | project: ['./tsconfig.json'],
17 | ecmaVersion: 2021,
18 | sourceType: 'module',
19 | ecmaFeatures: {
20 | jsx: true,
21 | },
22 | },
23 | plugins: ['@typescript-eslint'],
24 | rules: {
25 | '@typescript-eslint/no-explicit-any': 'off',
26 | '@typescript-eslint/explicit-module-boundary-types': 'off',
27 | '@typescript-eslint/no-inferrable-types': 'off',
28 | '@typescript-eslint/no-non-null-assertion': 'off',
29 | '@typescript-eslint/no-empty-interface': 'off',
30 | '@typescript-eslint/no-namespace': 'off',
31 | '@typescript-eslint/no-empty-function': 'off',
32 | '@typescript-eslint/no-this-alias': 'off',
33 | '@typescript-eslint/ban-types': 'off',
34 | '@typescript-eslint/ban-ts-comment': 'off',
35 | 'prefer-spread': 'off',
36 | 'no-case-declarations': 'off',
37 | 'no-console': 'off',
38 | '@typescript-eslint/no-unused-vars': ['error'],
39 | },
40 | };
41 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "qwikjs-wrap-balancer",
3 | "version": "0.6.0",
4 | "description": "Simple Qwik Component That Makes Titles More Readable",
5 | "main": "./lib/index.qwik.mjs",
6 | "qwik": "./lib/index.qwik.mjs",
7 | "types": "./lib-types/index.d.ts",
8 | "author": {
9 | "email": "harshdev8218@gmail.com",
10 | "name": "Harsh Mangalam"
11 | },
12 | "repository": {
13 | "url": "https://github.com/harshmangalam/qwik-wrap-balancer"
14 | },
15 | "exports": {
16 | ".": {
17 | "import": "./lib/index.qwik.mjs",
18 | "require": "./lib/index.qwik.cjs",
19 | "types": "./lib-types/index.d.ts"
20 | }
21 | },
22 | "files": [
23 | "lib",
24 | "lib-types"
25 | ],
26 | "keywords": [
27 | "qwik"
28 | ],
29 | "license": "MIT",
30 | "engines": {
31 | "node": ">=15.0.0"
32 | },
33 | "private": false,
34 | "type": "module",
35 | "scripts": {
36 | "build": "qwik build",
37 | "build.lib": "vite build --mode lib",
38 | "build.types": "tsc --emitDeclarationOnly",
39 | "dev": "vite --mode ssr",
40 | "dev.debug": "node --inspect-brk ./node_modules/vite/bin/vite.js --mode ssr --force",
41 | "fmt": "prettier --write .",
42 | "fmt.check": "prettier --check .",
43 | "lint": "eslint \"src/**/*.ts*\"",
44 | "release": "np",
45 | "start": "vite --open --mode ssr",
46 | "qwik": "qwik"
47 | },
48 | "devDependencies": {
49 | "@builder.io/qwik": "1.1.4",
50 | "@types/eslint": "8.4.10",
51 | "@types/node": "^18.11.18",
52 | "@types/node-fetch": "latest",
53 | "@typescript-eslint/eslint-plugin": "5.48.0",
54 | "@typescript-eslint/parser": "5.48.0",
55 | "eslint": "8.31.0",
56 | "eslint-plugin-qwik": "latest",
57 | "node-fetch": "3.3.0",
58 | "np": "7.6.1",
59 | "prettier": "2.8.1",
60 | "typescript": "4.9.4",
61 | "undici": "5.14.0",
62 | "vite": "4.0.3"
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/src/components/wrap-balancer.tsx:
--------------------------------------------------------------------------------
1 | import {
2 | $,
3 | component$,
4 | Slot,
5 | useVisibleTask$,
6 | useSignal,
7 | type HTMLAttributes,
8 | } from "@builder.io/qwik";
9 |
10 | const SYMBOL_OBSERVER_KEY = "__wrap_o";
11 |
12 | interface WrapperElement extends HTMLElement {
13 | [SYMBOL_OBSERVER_KEY]?: ResizeObserver | undefined;
14 | }
15 |
16 | interface BalancerProps extends HTMLAttributes {
17 | /**
18 | * The HTML tag to use for the wrapper element.
19 | * @default 'span'
20 | */
21 | as?: any;
22 | /**
23 | * The balance ratio of the wrapper width (0 <= ratio <= 1).
24 | * 0 means the wrapper width is the same as the container width (no balance, browser default).
25 | * 1 means the wrapper width is the minimum (full balance, most compact).
26 | * @default 1
27 | */
28 | ratio?: number;
29 | }
30 |
31 | export const WrapBalancer = component$((props: BalancerProps) => {
32 | const { as: Wrapper = "span", ratio = 1 } = props;
33 | const wrapperRef = useSignal();
34 |
35 | const SYMBOL_KEY = "__wrap_b";
36 |
37 | const relayout = $((id: string, ratio: number, wrapper: WrapperElement) => {
38 | wrapper =
39 | wrapper || document.querySelector(`[data-br="${id}"]`);
40 | const container = wrapper.parentElement;
41 |
42 | const update = (width: number) => (wrapper.style.maxWidth = width + "px");
43 |
44 | // Reset wrapper width
45 | wrapper.style.maxWidth = "";
46 |
47 | // Get the initial container size
48 | const width = container?.clientWidth;
49 | const height = container?.clientHeight;
50 |
51 | // Synchronously do binary search and calculate the layout
52 | let left = (width as number) / 2;
53 | let right = width as number;
54 | let middle: number;
55 |
56 | if (width) {
57 | while (left + 1 < right) {
58 | middle = ~~((left + right) / 2);
59 | update(middle);
60 | if (container.clientHeight === height) {
61 | right = middle;
62 | } else {
63 | left = middle;
64 | }
65 | }
66 |
67 | // Update the wrapper width
68 | update(right * ratio + width * (1 - ratio));
69 | }
70 |
71 | // Create a new observer if we don't have one.
72 | // Note that we must inline the key here as we use `toString()` to serialize
73 | // the function.
74 | if (!wrapper["__wrap_o"]) {
75 | (wrapper["__wrap_o"] = new ResizeObserver(() => {
76 | (self as any).__wrap_b(0, +(wrapper.dataset.brr as any), wrapper);
77 | })).observe(container as HTMLElement);
78 | }
79 | });
80 |
81 | useVisibleTask$(({ track }) => {
82 | track(() => ratio);
83 | track(() => wrapperRef.value);
84 | if (wrapperRef.value) {
85 | // Re-assign the function here as the component can be dynamically rendered, and script tag won't work in that case.
86 | ((self as any)[SYMBOL_KEY] = relayout)(
87 | "balancer",
88 | ratio,
89 | wrapperRef.value
90 | );
91 | }
92 | });
93 |
94 | return (
95 | <>
96 |
107 |
108 |
109 | >
110 | );
111 | });
112 |
--------------------------------------------------------------------------------