@brillout/docpress 0.16.26 → 0.16.28

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.
@@ -2,29 +2,139 @@
2
2
  position: relative;
3
3
 
4
4
  &:hover {
5
- .select-choice,
5
+ .select-container,
6
6
  .copy-button {
7
7
  opacity: 1;
8
8
  }
9
9
  }
10
10
 
11
11
  /* layout */
12
- --select-width: 85px;
13
- --select-top-position: 10px;
14
- --select-right-base: 42px;
15
- --select-gap: 2px;
12
+ --top-position: 10px;
13
+ --right-base: 42px;
14
+ --right-offset: 0px;
15
+ --border-color: hsl(0, 0%, 75%) hsl(0, 0%, 72%) hsl(0, 0%, 72%) hsl(0, 0%, 75%);
16
16
 
17
- /* each select increments the level */
18
- .select-choice {
17
+ .select-container {
19
18
  position: absolute;
19
+ background: #eee;
20
20
  z-index: 3;
21
- height: 25px;
22
- top: var(--select-top-position);
23
- width: var(--select-width);
24
- right: calc(var(--select-right-base) + (var(--lvl) * (var(--select-width) + var(--select-gap))));
21
+ font-size: 13.3333px;
22
+ top: var(--top-position);
23
+ right: calc(var(--right-base) + (var(--right-offset)));
24
+
25
+ -webkit-user-select: none; /* Safari */
26
+ -moz-user-select: none; /* Firefox */
27
+ -ms-user-select: none; /* IE/Edge legacy */
28
+ user-select: none; /* Standard */
29
+
30
+ border-radius: 5px;
31
+ border-style: solid;
32
+ border-color: var(--border-color);
25
33
 
26
34
  opacity: 0;
27
- transition: opacity 0.5s ease-in-out, background-color 0.4s ease-in-out;
35
+ transition: opacity 0.5s ease-in-out;
36
+ }
37
+
38
+ .sliding-rectangle {
39
+ position: relative;
40
+ border-radius: 5px;
41
+ border-color: var(--border-color);
42
+ width: 100%;
43
+ overflow: hidden;
44
+ transition: top 180ms cubic-bezier(0.2, 0.9, 0.2, 1);
45
+ }
46
+
47
+ .select-choice {
48
+ display: flex;
49
+ white-space: nowrap;
50
+ align-items: center;
51
+ flex-wrap: nowrap;
52
+ background: #fff;
53
+ padding: 0 3px 0 5px;
54
+ border-bottom: 1px solid rgba(0, 0, 0, 0.1);
55
+ cursor: pointer;
56
+ transition: background 120ms ease;
57
+
58
+ span {
59
+ flex: 1;
60
+ }
61
+
62
+ &::after {
63
+ width: 15px;
64
+ text-align: end;
65
+ --animation-width: 2px;
66
+ position: relative;
67
+ padding-right: var(--animation-width);
68
+ padding-left: var(--animation-width);
69
+ font-size: 1.13em;
70
+ color: #666;
71
+ left: 0;
72
+ transition: left 500ms ease, opacity 150ms ease-in-out;
73
+ opacity: 0;
74
+ }
75
+ .sliding-rectangle:hover &:hover::after {
76
+ opacity: 1;
77
+ }
78
+ &[aria-selected='true'] {
79
+ &::after {
80
+ content: '»' !important;
81
+ opacity: 1;
82
+ }
83
+ .sliding-rectangle:hover &:not(:hover)::after {
84
+ opacity: 0;
85
+ }
86
+ &:hover::after {
87
+ left: var(--animation-width);
88
+ }
89
+ }
90
+ &[aria-disabled='true'] {
91
+ &::after {
92
+ content: '⊘';
93
+ font-size: 1em;
94
+ left: 1px;
95
+ }
96
+ }
97
+ &:not([aria-selected='true']):not([aria-disabled='true']) {
98
+ &::after {
99
+ content: '«';
100
+ }
101
+ &:hover::after {
102
+ left: calc(-1 * var(--animation-width));
103
+ }
104
+ }
105
+ }
106
+
107
+ .select-container[aria-expanded='false'] {
108
+ overflow: hidden;
109
+ border-width: 1px 2px 2px 1px;
110
+ }
111
+
112
+ .select-container[aria-expanded='true'] {
113
+ overflow: visible;
114
+ border-width: 0;
115
+
116
+ .sliding-rectangle {
117
+ border-style: solid;
118
+ border-width: 1px 2px 2px 1px;
119
+ }
120
+ }
121
+
122
+ .select-choice:last-child {
123
+ border-bottom: none;
124
+ }
125
+
126
+ .select-choice:hover {
127
+ background: #f5f5f5;
128
+ }
129
+
130
+ .select-choice[aria-selected='true'] {
131
+ background: #eee;
132
+ }
133
+
134
+ .select-choice[aria-disabled='true'] {
135
+ color: #999;
136
+ cursor: default;
137
+ opacity: 0.8;
28
138
  }
29
139
 
30
140
  .hidden {
@@ -1,6 +1,6 @@
1
1
  export { ChoiceGroup }
2
2
 
3
- import React from 'react'
3
+ import React, { useEffect, useRef, useState } from 'react'
4
4
  import { useSelectedChoice } from '../hooks/useSelectedChoice.js'
5
5
  import { useRestoreScroll } from '../hooks/useRestoreScroll.js'
6
6
  import { cls } from '../../utils/cls.js'
@@ -18,32 +18,99 @@ function ChoiceGroup({
18
18
  choiceGroup,
19
19
  lvl,
20
20
  hide = false,
21
- }: { children: React.ReactNode; choiceGroup: TChoiceGroup; lvl: number; hide: boolean }) {
22
- const [selectedChoice, setSelectedChoice] = useSelectedChoice(choiceGroup.name, choiceGroup.default)
21
+ }: { children: React.ReactNode; choiceGroup: TChoiceGroup; lvl: string; hide: boolean }) {
22
+ const level = Number(lvl)
23
+ const { name: groupName, choices, default: defaultChoice, disabled: disabledChoices } = choiceGroup
24
+ // TODO/after-PR-merge rename useSelectedChoice useCurrentSelection
25
+ const [selectedChoice, setSelectedChoice] = useSelectedChoice(groupName, defaultChoice)
23
26
  const prevPositionRef = useRestoreScroll([selectedChoice])
27
+ const choiceGroupRef = useRef<HTMLDivElement>(null)
28
+ const [rightOffset, setRightOffset] = useState(0)
29
+
30
+ useEffect(() => {
31
+ if (level === 0 || !choiceGroupRef.current) return
32
+ const parentCustomSelect = choiceGroupRef.current.closest(`[data-lvl="${level - 1}"]`)!.lastElementChild!
33
+ const width = parentCustomSelect.getBoundingClientRect().width
34
+
35
+ setRightOffset(level * width + 2)
36
+ }, [])
37
+
38
+ const isDisabled = (choice: string) => disabledChoices.includes(choice)
39
+ const selectedIndex = choices.indexOf(selectedChoice)
40
+
41
+ const height = 25
42
+ const [expanded, setExpanded] = useState(false)
43
+ const rectTop = -selectedIndex * height
44
+
45
+ // Cycle to next option
46
+ const next = () => {
47
+ let nextIndex = selectedIndex
48
+
49
+ for (let i = 0; i < choices.length; i++) {
50
+ nextIndex = (nextIndex + 1) % choices.length
51
+ if (!isDisabled(choices[nextIndex]!)) {
52
+ setSelectedChoice(choices[nextIndex]!)
53
+ return
54
+ }
55
+ }
56
+ }
24
57
 
25
58
  return (
26
- <div data-choice-group={choiceGroup.name} className="choice-group">
27
- <select
28
- name={`choicesFor-${choiceGroup.name}`}
29
- value={selectedChoice}
30
- onChange={onChange}
31
- className={cls(['select-choice', hide && 'hidden'])}
32
- style={{ '--lvl': lvl }}
33
- >
59
+ <div ref={choiceGroupRef} data-choice-group={groupName} data-lvl={level} className="choice-group">
60
+ {/* Hidden select used to control choice visibility via CSS */}
61
+ <select name={`choicesFor-${groupName}`} value={selectedChoice} hidden disabled>
34
62
  {choiceGroup.choices.map((choice, i) => (
35
- <option key={i} value={choice} disabled={choiceGroup.disabled.includes(choice)}>
63
+ <option key={i} value={choice}>
36
64
  {choice}
37
65
  </option>
38
66
  ))}
39
67
  </select>
40
68
  {children}
69
+ <div
70
+ id={`choicesFor-${groupName}`}
71
+ aria-haspopup="listbox"
72
+ aria-expanded={expanded}
73
+ className={cls(['select-container', (hide || isDisabled(selectedChoice)) && 'hidden'])}
74
+ style={{ height, '--right-offset': `${rightOffset}px` }}
75
+ onMouseEnter={() => setExpanded(true)}
76
+ onMouseLeave={() => setExpanded(false)}
77
+ onClick={() => {
78
+ if (!expanded) next()
79
+ }}
80
+ >
81
+ <div
82
+ aria-activedescendant={`choice-${selectedChoice}`}
83
+ role="listbox"
84
+ className="sliding-rectangle"
85
+ style={{ top: rectTop, height: choices.length * height }}
86
+ >
87
+ {choices.map((choice, i) => (
88
+ <div
89
+ id={choice}
90
+ key={i}
91
+ aria-selected={i === selectedIndex}
92
+ aria-disabled={isDisabled(choice)}
93
+ role="option"
94
+ className="select-choice"
95
+ style={{ height }}
96
+ onClick={handleOnClick}
97
+ >
98
+ <span>{choice}</span>
99
+ </div>
100
+ ))}
101
+ </div>
102
+ </div>
41
103
  </div>
42
104
  )
43
105
 
44
- function onChange(e: React.ChangeEvent<HTMLSelectElement>) {
45
- const el = e.target
106
+ function handleOnClick(e: React.MouseEvent<HTMLDivElement, MouseEvent>) {
107
+ e.stopPropagation()
108
+ const el = e.currentTarget
46
109
  prevPositionRef.current = { top: el.getBoundingClientRect().top, el }
47
- setSelectedChoice(el.value)
110
+ if (el.ariaSelected === 'true') {
111
+ next()
112
+ } else if (el.ariaDisabled === 'false') {
113
+ setSelectedChoice(el.id)
114
+ }
48
115
  }
49
116
  }
@@ -25,7 +25,7 @@ function initializeChoiceGroup() {
25
25
  const storageKey = `docpress:choice:${choiceGroupName}`
26
26
  const selectedChoice = localStorage.getItem(storageKey)
27
27
  if (selectedChoice) {
28
- const selectEl = groupEl.querySelector<HTMLSelectElement>(`.select-choice`)!
28
+ const selectEl = groupEl.querySelector<HTMLSelectElement>(`select[name="choicesFor-${choiceGroupName}"]`)!
29
29
  const selectedIndex = [...selectEl.options].findIndex((option) => option.value === selectedChoice)
30
30
  if (selectedIndex === -1) {
31
31
  localStorage.removeItem(storageKey)
@@ -16,6 +16,7 @@ function Link({
16
16
  doNotInferSectionTitle,
17
17
  noWarning,
18
18
  children,
19
+ ...props
19
20
  }: {
20
21
  href: string
21
22
  text?: string | React.ReactNode
@@ -23,7 +24,7 @@ function Link({
23
24
  doNotInferSectionTitle?: boolean
24
25
  noWarning?: boolean
25
26
  children?: React.ReactNode
26
- }) {
27
+ } & React.AnchorHTMLAttributes<HTMLAnchorElement>) {
27
28
  const pageContext = usePageContext()
28
29
  assertUsage(
29
30
  href.startsWith('/') || href.startsWith('#'),
@@ -45,7 +46,11 @@ function Link({
45
46
  }
46
47
  }
47
48
 
48
- return <a href={href}>{text}</a>
49
+ return (
50
+ <a {...props} href={href}>
51
+ {text}
52
+ </a>
53
+ )
49
54
  }
50
55
 
51
56
  function getLinkText({
@@ -2,14 +2,14 @@ export { Link };
2
2
  export type { LinkData };
3
3
  import React from 'react';
4
4
  import type { StringArray } from '../types/Heading.js';
5
- declare function Link({ href, text, noBreadcrumb, doNotInferSectionTitle, noWarning, children, }: {
5
+ declare function Link({ href, text, noBreadcrumb, doNotInferSectionTitle, noWarning, children, ...props }: {
6
6
  href: string;
7
7
  text?: string | React.ReactNode;
8
8
  noBreadcrumb?: boolean;
9
9
  doNotInferSectionTitle?: boolean;
10
10
  noWarning?: boolean;
11
11
  children?: React.ReactNode;
12
- }): React.JSX.Element;
12
+ } & React.AnchorHTMLAttributes<HTMLAnchorElement>): React.JSX.Element;
13
13
  type LinkData = {
14
14
  url?: null | string;
15
15
  title: string;
@@ -4,7 +4,7 @@ import { usePageContext } from '../renderer/usePageContext.js';
4
4
  import { assert, assertUsage, assertWarning, determineSectionTitle, determineSectionUrlHash } from '../utils/server.js';
5
5
  import { parseMarkdownMini } from '../parseMarkdownMini.js';
6
6
  import pc from '@brillout/picocolors';
7
- function Link({ href, text, noBreadcrumb, doNotInferSectionTitle, noWarning, children, }) {
7
+ function Link({ href, text, noBreadcrumb, doNotInferSectionTitle, noWarning, children, ...props }) {
8
8
  const pageContext = usePageContext();
9
9
  assertUsage(href.startsWith('/') || href.startsWith('#'), `<Link href /> prop \`href==='${href}'\` but should start with '/' or '#'`);
10
10
  assertUsage(!text || !children, 'Cannot use both `text` or `children`');
@@ -22,7 +22,7 @@ function Link({ href, text, noBreadcrumb, doNotInferSectionTitle, noWarning, chi
22
22
  text = 'LINK-TARGET-NOT-FOUND';
23
23
  }
24
24
  }
25
- return React.createElement("a", { href: href }, text);
25
+ return (React.createElement("a", { ...props, href: href }, text));
26
26
  }
27
27
  function getLinkText({ noBreadcrumb, linkData, sectionTitle, isLinkOnSamePage, }) {
28
28
  const breadcrumbParts = [];
@@ -1,6 +1,6 @@
1
1
  export { config as viteConfig };
2
2
  import mdx from '@mdx-js/rollup';
3
- import react from '@vitejs/plugin-react-swc';
3
+ import react from '@vitejs/plugin-react';
4
4
  import { parsePageSections } from './parsePageSections.js';
5
5
  import rehypePrettyCode from 'rehype-pretty-code';
6
6
  import remarkGfm from 'remark-gfm';
@@ -37,7 +37,6 @@ const config = {
37
37
  plugins: [
38
38
  parsePageSections(),
39
39
  mdx({ rehypePlugins, remarkPlugins, providerImportSource: '@brillout/docpress' }),
40
- // @vitejs/plugin-react-swc needs to be added *after* the mdx plugins
41
40
  react(),
42
41
  ],
43
42
  optimizeDeps: {
@@ -23,9 +23,6 @@ function DocSearchInstall() {
23
23
  hitsPerPage: 50,
24
24
  }}
25
25
  transformItems={(hits) => {
26
- hits.map((hit) => {
27
- if (hit.type === 'lvl1') hit.url = hit.url.split('#')[0]!
28
- })
29
26
  hits.sort((a, b) => Number(a.url.includes('#')) - Number(b.url.includes('#')))
30
27
  return hits
31
28
  }}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@brillout/docpress",
3
- "version": "0.16.26",
3
+ "version": "0.16.28",
4
4
  "type": "module",
5
5
  "dependencies": {
6
6
  "@brillout/picocolors": "^1.0.10",
@@ -11,7 +11,7 @@
11
11
  "@mdx-js/react": "3.0.1",
12
12
  "@mdx-js/rollup": "3.0.1",
13
13
  "@shikijs/transformers": "^4.0.2",
14
- "@vitejs/plugin-react-swc": "^3.10.2",
14
+ "@vitejs/plugin-react": "^6.0.1",
15
15
  "detype": "^2.0.2",
16
16
  "estree-util-value-to-estree": "^3.5.0",
17
17
  "npm-to-yarn": "^3.0.1",
@@ -20,18 +20,18 @@
20
20
  "remark-gfm": "4.0.0",
21
21
  "shiki": "1.2.0",
22
22
  "unist-util-visit": "^5.0.0",
23
- "vite": "^6.3.5"
23
+ "vite": "^8.0.8"
24
24
  },
25
25
  "peerDependencies": {
26
- "@vitejs/plugin-react-swc": ">=3.0.0",
26
+ "@vitejs/plugin-react": ">=6.0.0",
27
27
  "react": ">=18.0.0",
28
28
  "react-dom": ">=18.0.0",
29
29
  "typescript": ">=5.0.0",
30
30
  "vike": ">=0.4.234",
31
- "vite": ">=5.2.0"
31
+ "vite": ">=8.0.0"
32
32
  },
33
33
  "peerDependenciesMeta": {
34
- "@vitejs/plugin-react-swc": {
34
+ "@vitejs/plugin-react": {
35
35
  "optional": true
36
36
  },
37
37
  "vite": {
package/vite.config.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  export { config as viteConfig }
2
2
 
3
3
  import mdx from '@mdx-js/rollup'
4
- import react from '@vitejs/plugin-react-swc'
4
+ import react from '@vitejs/plugin-react'
5
5
  import type { PluginOption, UserConfig } from 'vite'
6
6
  import { parsePageSections } from './parsePageSections.js'
7
7
  import rehypePrettyCode from 'rehype-pretty-code'
@@ -41,7 +41,6 @@ const config: UserConfig = {
41
41
  plugins: [
42
42
  parsePageSections(),
43
43
  mdx({ rehypePlugins, remarkPlugins, providerImportSource: '@brillout/docpress' }) as PluginOption,
44
- // @vitejs/plugin-react-swc needs to be added *after* the mdx plugins
45
44
  react(),
46
45
  ],
47
46
  optimizeDeps: {
package/AGENTS.md DELETED
@@ -1,30 +0,0 @@
1
- 🚨 STOP: Before responding to ANY message, you MUST complete STEP 1 below. No exceptions - even for simple questions like "1+1".
2
-
3
- @AI: Read and always respect all instructions in all AGENTS.md files. Treat this context as always up-to-date — it's never out of scope, no matter how much other context you have.
4
-
5
- List of `AGENTS.md` files:
6
- 1. This file (project preferences)
7
- 2. `~/.config/AGENTS.md` (user preferences)
8
-
9
- # CRITICAL AGENT INSTRUCTIONS - MUST FOLLOW
10
-
11
- ## What is "ANY agentic session"?
12
- - User asks a question (even "1+1" or simple queries)
13
- - User requests a code change
14
- - User asks for information
15
- - Session resumes after idle time
16
- - First interaction in a conversation
17
- - EVERYTHING counts - no exceptions
18
-
19
- ## STEP 1: ALWAYS START HERE (BEFORE ANY TASK)
20
- **MANDATORY:** When starting ANY agentic session, you MUST:
21
- 1. Read both AGENTS.md files (this file + ~/.config/AGENTS.md)
22
- 2. Say "I've successfully read [list-of-AGENTS.md-files]"
23
- 3. List ALL instructions from both files
24
- 4. Confirm: "✅ AGENTS.md check complete, proceeding with: [brief task description]"
25
- 5. Then proceed with the task
26
-
27
- ## STEP 2: AFTER MAKING CHANGES
28
- **MANDATORY:** After completing a task and if you made changes:
29
- 1. Run `$ pnpm run -w format`
30
- 2. Follow user preferences from ~/.config/AGENTS.md for commits and notifications