@react-x11/components 0.1.0 → 0.2.1
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.
- package/README.md +979 -21
- package/dist/ansi/cast.d.ts +48 -0
- package/dist/ansi/cast.d.ts.map +1 -0
- package/dist/ansi/cast.js +135 -0
- package/dist/ansi/cast.js.map +1 -0
- package/dist/ansi/index.d.ts +10 -0
- package/dist/ansi/index.d.ts.map +1 -0
- package/dist/ansi/index.js +27 -0
- package/dist/ansi/index.js.map +1 -0
- package/dist/ansi/palette.d.ts +79 -0
- package/dist/ansi/palette.d.ts.map +1 -0
- package/dist/ansi/palette.js +155 -0
- package/dist/ansi/palette.js.map +1 -0
- package/dist/ansi/parse.d.ts +114 -0
- package/dist/ansi/parse.d.ts.map +1 -0
- package/dist/ansi/parse.js +687 -0
- package/dist/ansi/parse.js.map +1 -0
- package/dist/ansi/sgr.d.ts +87 -0
- package/dist/ansi/sgr.d.ts.map +1 -0
- package/dist/ansi/sgr.js +302 -0
- package/dist/ansi/sgr.js.map +1 -0
- package/dist/ansi/utf8.d.ts +15 -0
- package/dist/ansi/utf8.d.ts.map +1 -0
- package/dist/ansi/utf8.js +82 -0
- package/dist/ansi/utf8.js.map +1 -0
- package/dist/calendar/Calendar.d.ts +136 -0
- package/dist/calendar/Calendar.d.ts.map +1 -0
- package/dist/calendar/Calendar.js +460 -0
- package/dist/calendar/Calendar.js.map +1 -0
- package/dist/calendar/DatePicker.d.ts +52 -0
- package/dist/calendar/DatePicker.d.ts.map +1 -0
- package/dist/calendar/DatePicker.js +257 -0
- package/dist/calendar/DatePicker.js.map +1 -0
- package/dist/calendar/dates.d.ts +90 -0
- package/dist/calendar/dates.d.ts.map +1 -0
- package/dist/calendar/dates.js +236 -0
- package/dist/calendar/dates.js.map +1 -0
- package/dist/calendar/index.d.ts +8 -0
- package/dist/calendar/index.d.ts.map +1 -0
- package/dist/calendar/index.js +15 -0
- package/dist/calendar/index.js.map +1 -0
- package/dist/charts/components.d.ts +141 -0
- package/dist/charts/components.d.ts.map +1 -0
- package/dist/charts/components.js +580 -0
- package/dist/charts/components.js.map +1 -0
- package/dist/charts/data.d.ts +198 -0
- package/dist/charts/data.d.ts.map +1 -0
- package/dist/charts/data.js +616 -0
- package/dist/charts/data.js.map +1 -0
- package/dist/charts/hx.d.ts +21 -0
- package/dist/charts/hx.d.ts.map +1 -0
- package/dist/charts/hx.js +15 -0
- package/dist/charts/hx.js.map +1 -0
- package/dist/charts/index.d.ts +27 -0
- package/dist/charts/index.d.ts.map +1 -0
- package/dist/charts/index.js +38 -0
- package/dist/charts/index.js.map +1 -0
- package/dist/charts/node.d.ts +96 -0
- package/dist/charts/node.d.ts.map +1 -0
- package/dist/charts/node.js +720 -0
- package/dist/charts/node.js.map +1 -0
- package/dist/charts/render.d.ts +153 -0
- package/dist/charts/render.d.ts.map +1 -0
- package/dist/charts/render.js +762 -0
- package/dist/charts/render.js.map +1 -0
- package/dist/charts/scale.d.ts +42 -0
- package/dist/charts/scale.d.ts.map +1 -0
- package/dist/charts/scale.js +173 -0
- package/dist/charts/scale.js.map +1 -0
- package/dist/charts/spec.d.ts +91 -0
- package/dist/charts/spec.d.ts.map +1 -0
- package/dist/charts/spec.js +33 -0
- package/dist/charts/spec.js.map +1 -0
- package/dist/charts/timers.d.ts +6 -0
- package/dist/charts/timers.d.ts.map +1 -0
- package/dist/charts/timers.js +14 -0
- package/dist/charts/timers.js.map +1 -0
- package/dist/code/hx.d.ts +24 -0
- package/dist/code/hx.d.ts.map +1 -0
- package/dist/code/hx.js +25 -0
- package/dist/code/hx.js.map +1 -0
- package/dist/code/index.d.ts +45 -0
- package/dist/code/index.d.ts.map +1 -0
- package/dist/code/index.js +93 -0
- package/dist/code/index.js.map +1 -0
- package/dist/code-editor/completion.d.ts +26 -0
- package/dist/code-editor/completion.d.ts.map +1 -0
- package/dist/code-editor/completion.js +129 -0
- package/dist/code-editor/completion.js.map +1 -0
- package/dist/code-editor/doc.d.ts +35 -0
- package/dist/code-editor/doc.d.ts.map +1 -0
- package/dist/code-editor/doc.js +162 -0
- package/dist/code-editor/doc.js.map +1 -0
- package/dist/code-editor/hx.d.ts +13 -0
- package/dist/code-editor/hx.d.ts.map +1 -0
- package/dist/code-editor/hx.js +10 -0
- package/dist/code-editor/hx.js.map +1 -0
- package/dist/code-editor/index.d.ts +88 -0
- package/dist/code-editor/index.d.ts.map +1 -0
- package/dist/code-editor/index.js +401 -0
- package/dist/code-editor/index.js.map +1 -0
- package/dist/code-editor/node.d.ts +343 -0
- package/dist/code-editor/node.d.ts.map +1 -0
- package/dist/code-editor/node.js +1521 -0
- package/dist/code-editor/node.js.map +1 -0
- package/dist/code-language/hljs.d.ts +47 -0
- package/dist/code-language/hljs.d.ts.map +1 -0
- package/dist/code-language/hljs.js +211 -0
- package/dist/code-language/hljs.js.map +1 -0
- package/dist/code-language/index.d.ts +22 -0
- package/dist/code-language/index.d.ts.map +1 -0
- package/dist/code-language/index.js +20 -0
- package/dist/code-language/index.js.map +1 -0
- package/dist/code-language/languages/glsl.d.ts +4 -0
- package/dist/code-language/languages/glsl.d.ts.map +1 -0
- package/dist/code-language/languages/glsl.js +99 -0
- package/dist/code-language/languages/glsl.js.map +1 -0
- package/dist/code-language/languages/javascript.d.ts +10 -0
- package/dist/code-language/languages/javascript.d.ts.map +1 -0
- package/dist/code-language/languages/javascript.js +360 -0
- package/dist/code-language/languages/javascript.js.map +1 -0
- package/dist/code-language/languages/json.d.ts +4 -0
- package/dist/code-language/languages/json.d.ts.map +1 -0
- package/dist/code-language/languages/json.js +68 -0
- package/dist/code-language/languages/json.js.map +1 -0
- package/dist/code-language/languages/shell.d.ts +4 -0
- package/dist/code-language/languages/shell.d.ts.map +1 -0
- package/dist/code-language/languages/shell.js +189 -0
- package/dist/code-language/languages/shell.js.map +1 -0
- package/dist/code-language/languages/sql.d.ts +11 -0
- package/dist/code-language/languages/sql.d.ts.map +1 -0
- package/dist/code-language/languages/sql.js +133 -0
- package/dist/code-language/languages/sql.js.map +1 -0
- package/dist/code-language/lezer.d.ts +24 -0
- package/dist/code-language/lezer.d.ts.map +1 -0
- package/dist/code-language/lezer.js +148 -0
- package/dist/code-language/lezer.js.map +1 -0
- package/dist/code-language/registry.d.ts +19 -0
- package/dist/code-language/registry.d.ts.map +1 -0
- package/dist/code-language/registry.js +56 -0
- package/dist/code-language/registry.js.map +1 -0
- package/dist/code-language/runs.d.ts +35 -0
- package/dist/code-language/runs.d.ts.map +1 -0
- package/dist/code-language/runs.js +88 -0
- package/dist/code-language/runs.js.map +1 -0
- package/dist/code-language/stream.d.ts +70 -0
- package/dist/code-language/stream.d.ts.map +1 -0
- package/dist/code-language/stream.js +243 -0
- package/dist/code-language/stream.js.map +1 -0
- package/dist/code-language/textmate.d.ts +27 -0
- package/dist/code-language/textmate.d.ts.map +1 -0
- package/dist/code-language/textmate.js +86 -0
- package/dist/code-language/textmate.js.map +1 -0
- package/dist/code-language/theme.d.ts +26 -0
- package/dist/code-language/theme.d.ts.map +1 -0
- package/dist/code-language/theme.js +108 -0
- package/dist/code-language/theme.js.map +1 -0
- package/dist/code-language/timers.d.ts +7 -0
- package/dist/code-language/timers.d.ts.map +1 -0
- package/dist/code-language/timers.js +22 -0
- package/dist/code-language/timers.js.map +1 -0
- package/dist/code-language/types.d.ts +173 -0
- package/dist/code-language/types.d.ts.map +1 -0
- package/dist/code-language/types.js +41 -0
- package/dist/code-language/types.js.map +1 -0
- package/dist/codeblock/index.d.ts +69 -0
- package/dist/codeblock/index.d.ts.map +1 -0
- package/dist/codeblock/index.js +85 -0
- package/dist/codeblock/index.js.map +1 -0
- package/dist/color-picker/ColorField.d.ts +39 -0
- package/dist/color-picker/ColorField.d.ts.map +1 -0
- package/dist/color-picker/ColorField.js +239 -0
- package/dist/color-picker/ColorField.js.map +1 -0
- package/dist/color-picker/ColorPicker.d.ts +109 -0
- package/dist/color-picker/ColorPicker.d.ts.map +1 -0
- package/dist/color-picker/ColorPicker.js +757 -0
- package/dist/color-picker/ColorPicker.js.map +1 -0
- package/dist/color-picker/color.d.ts +105 -0
- package/dist/color-picker/color.d.ts.map +1 -0
- package/dist/color-picker/color.js +313 -0
- package/dist/color-picker/color.js.map +1 -0
- package/dist/color-picker/index.d.ts +8 -0
- package/dist/color-picker/index.d.ts.map +1 -0
- package/dist/color-picker/index.js +19 -0
- package/dist/color-picker/index.js.map +1 -0
- package/dist/desktop-calendar/eds.d.ts +102 -0
- package/dist/desktop-calendar/eds.d.ts.map +1 -0
- package/dist/desktop-calendar/eds.js +356 -0
- package/dist/desktop-calendar/eds.js.map +1 -0
- package/dist/desktop-calendar/ical.d.ts +63 -0
- package/dist/desktop-calendar/ical.d.ts.map +1 -0
- package/dist/desktop-calendar/ical.js +57 -0
- package/dist/desktop-calendar/ical.js.map +1 -0
- package/dist/desktop-calendar/index.d.ts +72 -0
- package/dist/desktop-calendar/index.d.ts.map +1 -0
- package/dist/desktop-calendar/index.js +213 -0
- package/dist/desktop-calendar/index.js.map +1 -0
- package/dist/embed/client.d.ts +121 -0
- package/dist/embed/client.d.ts.map +1 -0
- package/dist/embed/client.js +203 -0
- package/dist/embed/client.js.map +1 -0
- package/dist/embed/host.d.ts +96 -0
- package/dist/embed/host.d.ts.map +1 -0
- package/dist/embed/host.js +266 -0
- package/dist/embed/host.js.map +1 -0
- package/dist/embed/index.d.ts +5 -0
- package/dist/embed/index.d.ts.map +1 -0
- package/dist/embed/index.js +13 -0
- package/dist/embed/index.js.map +1 -0
- package/dist/embed/timers.d.ts +6 -0
- package/dist/embed/timers.d.ts.map +1 -0
- package/dist/embed/timers.js +16 -0
- package/dist/embed/timers.js.map +1 -0
- package/dist/flow/draw.d.ts +45 -0
- package/dist/flow/draw.d.ts.map +1 -0
- package/dist/flow/draw.js +295 -0
- package/dist/flow/draw.js.map +1 -0
- package/dist/flow/index.d.ts +64 -0
- package/dist/flow/index.d.ts.map +1 -0
- package/dist/flow/index.js +306 -0
- package/dist/flow/index.js.map +1 -0
- package/dist/flow/model.d.ts +158 -0
- package/dist/flow/model.d.ts.map +1 -0
- package/dist/flow/model.js +566 -0
- package/dist/flow/model.js.map +1 -0
- package/dist/flow/node.d.ts +429 -0
- package/dist/flow/node.d.ts.map +1 -0
- package/dist/flow/node.js +3025 -0
- package/dist/flow/node.js.map +1 -0
- package/dist/flow/paths.d.ts +43 -0
- package/dist/flow/paths.d.ts.map +1 -0
- package/dist/flow/paths.js +327 -0
- package/dist/flow/paths.js.map +1 -0
- package/dist/flow/types.d.ts +561 -0
- package/dist/flow/types.d.ts.map +1 -0
- package/dist/flow/types.js +2 -0
- package/dist/flow/types.js.map +1 -0
- package/dist/formula/hx.d.ts +24 -0
- package/dist/formula/hx.d.ts.map +1 -0
- package/dist/formula/hx.js +25 -0
- package/dist/formula/hx.js.map +1 -0
- package/dist/formula/index.d.ts +74 -0
- package/dist/formula/index.d.ts.map +1 -0
- package/dist/formula/index.js +155 -0
- package/dist/formula/index.js.map +1 -0
- package/dist/formula/katex.d.ts +78 -0
- package/dist/formula/katex.d.ts.map +1 -0
- package/dist/formula/katex.js +130 -0
- package/dist/formula/katex.js.map +1 -0
- package/dist/formula/layout.d.ts +103 -0
- package/dist/formula/layout.d.ts.map +1 -0
- package/dist/formula/layout.js +716 -0
- package/dist/formula/layout.js.map +1 -0
- package/dist/formula/node.d.ts +50 -0
- package/dist/formula/node.d.ts.map +1 -0
- package/dist/formula/node.js +351 -0
- package/dist/formula/node.js.map +1 -0
- package/dist/html/controls.d.ts +48 -0
- package/dist/html/controls.d.ts.map +1 -0
- package/dist/html/controls.js +195 -0
- package/dist/html/controls.js.map +1 -0
- package/dist/html/css/cascade.d.ts +51 -0
- package/dist/html/css/cascade.d.ts.map +1 -0
- package/dist/html/css/cascade.js +552 -0
- package/dist/html/css/cascade.js.map +1 -0
- package/dist/html/css/parse.d.ts +74 -0
- package/dist/html/css/parse.d.ts.map +1 -0
- package/dist/html/css/parse.js +464 -0
- package/dist/html/css/parse.js.map +1 -0
- package/dist/html/css/style.d.ts +142 -0
- package/dist/html/css/style.d.ts.map +1 -0
- package/dist/html/css/style.js +1145 -0
- package/dist/html/css/style.js.map +1 -0
- package/dist/html/css/ua.d.ts +5 -0
- package/dist/html/css/ua.d.ts.map +1 -0
- package/dist/html/css/ua.js +171 -0
- package/dist/html/css/ua.js.map +1 -0
- package/dist/html/css/values.d.ts +78 -0
- package/dist/html/css/values.d.ts.map +1 -0
- package/dist/html/css/values.js +330 -0
- package/dist/html/css/values.js.map +1 -0
- package/dist/html/dom.d.ts +112 -0
- package/dist/html/dom.d.ts.map +1 -0
- package/dist/html/dom.js +374 -0
- package/dist/html/dom.js.map +1 -0
- package/dist/html/hx.d.ts +24 -0
- package/dist/html/hx.d.ts.map +1 -0
- package/dist/html/hx.js +29 -0
- package/dist/html/hx.js.map +1 -0
- package/dist/html/index.d.ts +137 -0
- package/dist/html/index.d.ts.map +1 -0
- package/dist/html/index.js +339 -0
- package/dist/html/index.js.map +1 -0
- package/dist/html/layout/block.d.ts +58 -0
- package/dist/html/layout/block.d.ts.map +1 -0
- package/dist/html/layout/block.js +724 -0
- package/dist/html/layout/block.js.map +1 -0
- package/dist/html/layout/boxes.d.ts +261 -0
- package/dist/html/layout/boxes.d.ts.map +1 -0
- package/dist/html/layout/boxes.js +725 -0
- package/dist/html/layout/boxes.js.map +1 -0
- package/dist/html/layout/flex.d.ts +16 -0
- package/dist/html/layout/flex.d.ts.map +1 -0
- package/dist/html/layout/flex.js +242 -0
- package/dist/html/layout/flex.js.map +1 -0
- package/dist/html/layout/floats.d.ts +50 -0
- package/dist/html/layout/floats.d.ts.map +1 -0
- package/dist/html/layout/floats.js +110 -0
- package/dist/html/layout/floats.js.map +1 -0
- package/dist/html/layout/inline.d.ts +44 -0
- package/dist/html/layout/inline.d.ts.map +1 -0
- package/dist/html/layout/inline.js +700 -0
- package/dist/html/layout/inline.js.map +1 -0
- package/dist/html/layout/table.d.ts +5 -0
- package/dist/html/layout/table.d.ts.map +1 -0
- package/dist/html/layout/table.js +340 -0
- package/dist/html/layout/table.js.map +1 -0
- package/dist/html/node.d.ts +149 -0
- package/dist/html/node.d.ts.map +1 -0
- package/dist/html/node.js +861 -0
- package/dist/html/node.js.map +1 -0
- package/dist/html/paint.d.ts +48 -0
- package/dist/html/paint.d.ts.map +1 -0
- package/dist/html/paint.js +495 -0
- package/dist/html/paint.js.map +1 -0
- package/dist/html/resources.d.ts +48 -0
- package/dist/html/resources.d.ts.map +1 -0
- package/dist/html/resources.js +156 -0
- package/dist/html/resources.js.map +1 -0
- package/dist/index.d.ts +48 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +92 -0
- package/dist/index.js.map +1 -0
- package/dist/internal/heights.d.ts +88 -0
- package/dist/internal/heights.d.ts.map +1 -0
- package/dist/internal/heights.js +203 -0
- package/dist/internal/heights.js.map +1 -0
- package/dist/internal/hx.d.ts +24 -0
- package/dist/internal/hx.d.ts.map +1 -0
- package/dist/internal/hx.js +27 -0
- package/dist/internal/hx.js.map +1 -0
- package/dist/internal/scroll.d.ts +62 -0
- package/dist/internal/scroll.d.ts.map +1 -0
- package/dist/internal/scroll.js +224 -0
- package/dist/internal/scroll.js.map +1 -0
- package/dist/internal/text.d.ts +6 -0
- package/dist/internal/text.d.ts.map +1 -0
- package/dist/internal/text.js +44 -0
- package/dist/internal/text.js.map +1 -0
- package/dist/internal/timers.d.ts +8 -0
- package/dist/internal/timers.d.ts.map +1 -0
- package/dist/internal/timers.js +22 -0
- package/dist/internal/timers.js.map +1 -0
- package/dist/internal/widget.d.ts +40 -0
- package/dist/internal/widget.d.ts.map +1 -0
- package/dist/internal/widget.js +44 -0
- package/dist/internal/widget.js.map +1 -0
- package/dist/markdown/ast.d.ts +126 -0
- package/dist/markdown/ast.d.ts.map +1 -0
- package/dist/markdown/ast.js +8 -0
- package/dist/markdown/ast.js.map +1 -0
- package/dist/markdown/hx.d.ts +24 -0
- package/dist/markdown/hx.d.ts.map +1 -0
- package/dist/markdown/hx.js +25 -0
- package/dist/markdown/hx.js.map +1 -0
- package/dist/markdown/index.d.ts +90 -0
- package/dist/markdown/index.d.ts.map +1 -0
- package/dist/markdown/index.js +413 -0
- package/dist/markdown/index.js.map +1 -0
- package/dist/markdown/parse.d.ts +8 -0
- package/dist/markdown/parse.d.ts.map +1 -0
- package/dist/markdown/parse.js +911 -0
- package/dist/markdown/parse.js.map +1 -0
- package/dist/markdown/spans.d.ts +20 -0
- package/dist/markdown/spans.d.ts.map +1 -0
- package/dist/markdown/spans.js +96 -0
- package/dist/markdown/spans.js.map +1 -0
- package/dist/media-player/backends.d.ts +64 -0
- package/dist/media-player/backends.d.ts.map +1 -0
- package/dist/media-player/backends.js +110 -0
- package/dist/media-player/backends.js.map +1 -0
- package/dist/media-player/control.d.ts +40 -0
- package/dist/media-player/control.d.ts.map +1 -0
- package/dist/media-player/control.js +44 -0
- package/dist/media-player/control.js.map +1 -0
- package/dist/media-player/hx.d.ts +24 -0
- package/dist/media-player/hx.d.ts.map +1 -0
- package/dist/media-player/hx.js +25 -0
- package/dist/media-player/hx.js.map +1 -0
- package/dist/media-player/index.d.ts +107 -0
- package/dist/media-player/index.d.ts.map +1 -0
- package/dist/media-player/index.js +219 -0
- package/dist/media-player/index.js.map +1 -0
- package/dist/media-player/mpv.d.ts +4 -0
- package/dist/media-player/mpv.d.ts.map +1 -0
- package/dist/media-player/mpv.js +120 -0
- package/dist/media-player/mpv.js.map +1 -0
- package/dist/media-player/vlc.d.ts +8 -0
- package/dist/media-player/vlc.d.ts.map +1 -0
- package/dist/media-player/vlc.js +83 -0
- package/dist/media-player/vlc.js.map +1 -0
- package/dist/richtext/index.d.ts +19 -0
- package/dist/richtext/index.d.ts.map +1 -0
- package/dist/richtext/index.js +4 -0
- package/dist/richtext/index.js.map +1 -0
- package/dist/richtext/links.d.ts +12 -0
- package/dist/richtext/links.d.ts.map +1 -0
- package/dist/richtext/links.js +64 -0
- package/dist/richtext/links.js.map +1 -0
- package/dist/richtext/menu.d.ts +11 -0
- package/dist/richtext/menu.d.ts.map +1 -0
- package/dist/richtext/menu.js +43 -0
- package/dist/richtext/menu.js.map +1 -0
- package/dist/richtext/node.d.ts +151 -0
- package/dist/richtext/node.d.ts.map +1 -0
- package/dist/richtext/node.js +255 -0
- package/dist/richtext/node.js.map +1 -0
- package/dist/richtext/runs.d.ts +92 -0
- package/dist/richtext/runs.d.ts.map +1 -0
- package/dist/richtext/runs.js +158 -0
- package/dist/richtext/runs.js.map +1 -0
- package/dist/table/hx.d.ts +24 -0
- package/dist/table/hx.d.ts.map +1 -0
- package/dist/table/hx.js +25 -0
- package/dist/table/hx.js.map +1 -0
- package/dist/table/index.d.ts +169 -0
- package/dist/table/index.d.ts.map +1 -0
- package/dist/table/index.js +890 -0
- package/dist/table/index.js.map +1 -0
- package/dist/table/rows.d.ts +136 -0
- package/dist/table/rows.d.ts.map +1 -0
- package/dist/table/rows.js +146 -0
- package/dist/table/rows.js.map +1 -0
- package/dist/terminal/backends.d.ts +105 -0
- package/dist/terminal/backends.d.ts.map +1 -0
- package/dist/terminal/backends.js +185 -0
- package/dist/terminal/backends.js.map +1 -0
- package/dist/terminal/hx.d.ts +24 -0
- package/dist/terminal/hx.d.ts.map +1 -0
- package/dist/terminal/hx.js +25 -0
- package/dist/terminal/hx.js.map +1 -0
- package/dist/terminal/index.d.ts +160 -0
- package/dist/terminal/index.d.ts.map +1 -0
- package/dist/terminal/index.js +344 -0
- package/dist/terminal/index.js.map +1 -0
- package/dist/terminal/title.d.ts +24 -0
- package/dist/terminal/title.d.ts.map +1 -0
- package/dist/terminal/title.js +129 -0
- package/dist/terminal/title.js.map +1 -0
- package/dist/terminal/vt/colors.d.ts +63 -0
- package/dist/terminal/vt/colors.d.ts.map +1 -0
- package/dist/terminal/vt/colors.js +203 -0
- package/dist/terminal/vt/colors.js.map +1 -0
- package/dist/terminal/vt/diff.d.ts +117 -0
- package/dist/terminal/vt/diff.d.ts.map +1 -0
- package/dist/terminal/vt/diff.js +358 -0
- package/dist/terminal/vt/diff.js.map +1 -0
- package/dist/terminal/vt/fonts.d.ts +92 -0
- package/dist/terminal/vt/fonts.d.ts.map +1 -0
- package/dist/terminal/vt/fonts.js +178 -0
- package/dist/terminal/vt/fonts.js.map +1 -0
- package/dist/terminal/vt/index.d.ts +78 -0
- package/dist/terminal/vt/index.d.ts.map +1 -0
- package/dist/terminal/vt/index.js +312 -0
- package/dist/terminal/vt/index.js.map +1 -0
- package/dist/terminal/vt/keys.d.ts +74 -0
- package/dist/terminal/vt/keys.d.ts.map +1 -0
- package/dist/terminal/vt/keys.js +309 -0
- package/dist/terminal/vt/keys.js.map +1 -0
- package/dist/terminal/vt/mouse.d.ts +44 -0
- package/dist/terminal/vt/mouse.d.ts.map +1 -0
- package/dist/terminal/vt/mouse.js +106 -0
- package/dist/terminal/vt/mouse.js.map +1 -0
- package/dist/terminal/vt/node.d.ts +246 -0
- package/dist/terminal/vt/node.d.ts.map +1 -0
- package/dist/terminal/vt/node.js +1068 -0
- package/dist/terminal/vt/node.js.map +1 -0
- package/dist/terminal/vt/pty.d.ts +81 -0
- package/dist/terminal/vt/pty.d.ts.map +1 -0
- package/dist/terminal/vt/pty.js +201 -0
- package/dist/terminal/vt/pty.js.map +1 -0
- package/dist/terminal/vt/renderer.d.ts +179 -0
- package/dist/terminal/vt/renderer.d.ts.map +1 -0
- package/dist/terminal/vt/renderer.js +357 -0
- package/dist/terminal/vt/renderer.js.map +1 -0
- package/dist/terminal/vt/timers.d.ts +6 -0
- package/dist/terminal/vt/timers.d.ts.map +1 -0
- package/dist/terminal/vt/timers.js +16 -0
- package/dist/terminal/vt/timers.js.map +1 -0
- package/dist/terminal/vt/xterm.d.ts +142 -0
- package/dist/terminal/vt/xterm.d.ts.map +1 -0
- package/dist/terminal/vt/xterm.js +32 -0
- package/dist/terminal/vt/xterm.js.map +1 -0
- package/dist/terminal-output/hx.d.ts +24 -0
- package/dist/terminal-output/hx.d.ts.map +1 -0
- package/dist/terminal-output/hx.js +25 -0
- package/dist/terminal-output/hx.js.map +1 -0
- package/dist/terminal-output/index.d.ts +92 -0
- package/dist/terminal-output/index.d.ts.map +1 -0
- package/dist/terminal-output/index.js +179 -0
- package/dist/terminal-output/index.js.map +1 -0
- package/dist/terminal-output/runs.d.ts +27 -0
- package/dist/terminal-output/runs.d.ts.map +1 -0
- package/dist/terminal-output/runs.js +66 -0
- package/dist/terminal-output/runs.js.map +1 -0
- package/dist/three/canvas.d.ts +46 -0
- package/dist/three/canvas.d.ts.map +1 -0
- package/dist/three/canvas.js +305 -0
- package/dist/three/canvas.js.map +1 -0
- package/dist/three/events.d.ts +64 -0
- package/dist/three/events.d.ts.map +1 -0
- package/dist/three/events.js +146 -0
- package/dist/three/events.js.map +1 -0
- package/dist/three/geometries.d.ts +89 -0
- package/dist/three/geometries.d.ts.map +1 -0
- package/dist/three/geometries.js +345 -0
- package/dist/three/geometries.js.map +1 -0
- package/dist/three/globals.d.ts +7 -0
- package/dist/three/globals.d.ts.map +1 -0
- package/dist/three/globals.js +20 -0
- package/dist/three/globals.js.map +1 -0
- package/dist/three/hx.d.ts +9 -0
- package/dist/three/hx.d.ts.map +1 -0
- package/dist/three/hx.js +10 -0
- package/dist/three/hx.js.map +1 -0
- package/dist/three/index.d.ts +22 -0
- package/dist/three/index.d.ts.map +1 -0
- package/dist/three/index.js +24 -0
- package/dist/three/index.js.map +1 -0
- package/dist/three/jsx-dev-runtime.d.ts +19 -0
- package/dist/three/jsx-dev-runtime.d.ts.map +1 -0
- package/dist/three/jsx-dev-runtime.js +2 -0
- package/dist/three/jsx-dev-runtime.js.map +1 -0
- package/dist/three/jsx-runtime.d.ts +19 -0
- package/dist/three/jsx-runtime.d.ts.map +1 -0
- package/dist/three/jsx-runtime.js +2 -0
- package/dist/three/jsx-runtime.js.map +1 -0
- package/dist/three/jsx.d.ts +198 -0
- package/dist/three/jsx.d.ts.map +1 -0
- package/dist/three/jsx.js +2 -0
- package/dist/three/jsx.js.map +1 -0
- package/dist/three/materials.d.ts +125 -0
- package/dist/three/materials.d.ts.map +1 -0
- package/dist/three/materials.js +229 -0
- package/dist/three/materials.js.map +1 -0
- package/dist/three/math.d.ts +146 -0
- package/dist/three/math.d.ts.map +1 -0
- package/dist/three/math.js +464 -0
- package/dist/three/math.js.map +1 -0
- package/dist/three/objects.d.ts +185 -0
- package/dist/three/objects.d.ts.map +1 -0
- package/dist/three/objects.js +309 -0
- package/dist/three/objects.js.map +1 -0
- package/dist/three/passes.d.ts +54 -0
- package/dist/three/passes.d.ts.map +1 -0
- package/dist/three/passes.js +78 -0
- package/dist/three/passes.js.map +1 -0
- package/dist/three/postprocess.d.ts +85 -0
- package/dist/three/postprocess.d.ts.map +1 -0
- package/dist/three/postprocess.js +503 -0
- package/dist/three/postprocess.js.map +1 -0
- package/dist/three/raycast.d.ts +24 -0
- package/dist/three/raycast.d.ts.map +1 -0
- package/dist/three/raycast.js +138 -0
- package/dist/three/raycast.js.map +1 -0
- package/dist/three/reconciler.d.ts +48 -0
- package/dist/three/reconciler.d.ts.map +1 -0
- package/dist/three/reconciler.js +416 -0
- package/dist/three/reconciler.js.map +1 -0
- package/dist/three/renderer-direct.d.ts +102 -0
- package/dist/three/renderer-direct.d.ts.map +1 -0
- package/dist/three/renderer-direct.js +782 -0
- package/dist/three/renderer-direct.js.map +1 -0
- package/dist/three/renderer-indirect.d.ts +68 -0
- package/dist/three/renderer-indirect.d.ts.map +1 -0
- package/dist/three/renderer-indirect.js +438 -0
- package/dist/three/renderer-indirect.js.map +1 -0
- package/dist/three/store.d.ts +81 -0
- package/dist/three/store.d.ts.map +1 -0
- package/dist/three/store.js +110 -0
- package/dist/three/store.js.map +1 -0
- package/dist/timeline/hx.d.ts +18 -0
- package/dist/timeline/hx.d.ts.map +1 -0
- package/dist/timeline/hx.js +10 -0
- package/dist/timeline/hx.js.map +1 -0
- package/dist/timeline/index.d.ts +152 -0
- package/dist/timeline/index.d.ts.map +1 -0
- package/dist/timeline/index.js +383 -0
- package/dist/timeline/index.js.map +1 -0
- package/dist/tray-host/hx.d.ts +24 -0
- package/dist/tray-host/hx.d.ts.map +1 -0
- package/dist/tray-host/hx.js +25 -0
- package/dist/tray-host/hx.js.map +1 -0
- package/dist/tray-host/index.d.ts +110 -0
- package/dist/tray-host/index.d.ts.map +1 -0
- package/dist/tray-host/index.js +191 -0
- package/dist/tray-host/index.js.map +1 -0
- package/dist/tray-host/manager.d.ts +137 -0
- package/dist/tray-host/manager.d.ts.map +1 -0
- package/dist/tray-host/manager.js +462 -0
- package/dist/tray-host/manager.js.map +1 -0
- package/dist/tray-host/notify.d.ts +22 -0
- package/dist/tray-host/notify.d.ts.map +1 -0
- package/dist/tray-host/notify.js +74 -0
- package/dist/tray-host/notify.js.map +1 -0
- package/dist/tray-host/protocol.d.ts +106 -0
- package/dist/tray-host/protocol.d.ts.map +1 -0
- package/dist/tray-host/protocol.js +191 -0
- package/dist/tray-host/protocol.js.map +1 -0
- package/dist/tree/hx.d.ts +24 -0
- package/dist/tree/hx.d.ts.map +1 -0
- package/dist/tree/hx.js +25 -0
- package/dist/tree/hx.js.map +1 -0
- package/dist/tree/index.d.ts +221 -0
- package/dist/tree/index.d.ts.map +1 -0
- package/dist/tree/index.js +751 -0
- package/dist/tree/index.js.map +1 -0
- package/dist/tree/internal.d.ts +18 -0
- package/dist/tree/internal.d.ts.map +1 -0
- package/dist/tree/internal.js +66 -0
- package/dist/tree/internal.js.map +1 -0
- package/dist/tree/rows.d.ts +177 -0
- package/dist/tree/rows.d.ts.map +1 -0
- package/dist/tree/rows.js +179 -0
- package/dist/tree/rows.js.map +1 -0
- package/package.json +166 -12
- package/src/ansi/cast.ts +159 -0
- package/src/ansi/index.ts +53 -0
- package/src/ansi/palette.ts +226 -0
- package/src/ansi/parse.ts +780 -0
- package/src/ansi/sgr.ts +380 -0
- package/src/ansi/utf8.ts +84 -0
- package/src/calendar/Calendar.ts +744 -0
- package/src/calendar/DatePicker.ts +368 -0
- package/src/calendar/dates.ts +307 -0
- package/src/calendar/index.ts +51 -0
- package/src/charts/components.ts +864 -0
- package/src/charts/data.ts +776 -0
- package/src/charts/hx.ts +39 -0
- package/src/charts/index.ts +99 -0
- package/src/charts/node.ts +932 -0
- package/src/charts/render.ts +982 -0
- package/src/charts/scale.ts +214 -0
- package/src/charts/spec.ts +122 -0
- package/src/charts/timers.ts +24 -0
- package/src/code/hx.ts +51 -0
- package/src/code/index.ts +174 -0
- package/src/code-editor/completion.ts +161 -0
- package/src/code-editor/doc.ts +198 -0
- package/src/code-editor/hx.ts +26 -0
- package/src/code-editor/index.ts +615 -0
- package/src/code-editor/node.ts +1926 -0
- package/src/code-language/hljs.ts +302 -0
- package/src/code-language/index.ts +51 -0
- package/src/code-language/languages/glsl.ts +120 -0
- package/src/code-language/languages/javascript.ts +419 -0
- package/src/code-language/languages/json.ts +72 -0
- package/src/code-language/languages/shell.ts +221 -0
- package/src/code-language/languages/sql.ts +165 -0
- package/src/code-language/lezer.ts +190 -0
- package/src/code-language/registry.ts +66 -0
- package/src/code-language/runs.ts +128 -0
- package/src/code-language/stream.ts +306 -0
- package/src/code-language/textmate.ts +134 -0
- package/src/code-language/theme.ts +124 -0
- package/src/code-language/timers.ts +36 -0
- package/src/code-language/types.ts +252 -0
- package/src/codeblock/index.ts +146 -0
- package/src/color-picker/ColorField.ts +333 -0
- package/src/color-picker/ColorPicker.ts +1069 -0
- package/src/color-picker/color.ts +399 -0
- package/src/color-picker/index.ts +50 -0
- package/src/desktop-calendar/eds.ts +484 -0
- package/src/desktop-calendar/ical.ts +119 -0
- package/src/desktop-calendar/index.ts +287 -0
- package/src/embed/client.ts +311 -0
- package/src/embed/host.ts +432 -0
- package/src/embed/index.ts +33 -0
- package/src/embed/timers.ts +32 -0
- package/src/flow/draw.ts +418 -0
- package/src/flow/index.ts +473 -0
- package/src/flow/model.ts +687 -0
- package/src/flow/node.ts +3694 -0
- package/src/flow/paths.ts +403 -0
- package/src/flow/types.ts +634 -0
- package/src/formula/hx.ts +51 -0
- package/src/formula/index.ts +243 -0
- package/src/formula/katex.ts +209 -0
- package/src/formula/layout.ts +876 -0
- package/src/formula/node.ts +467 -0
- package/src/html/controls.ts +244 -0
- package/src/html/css/cascade.ts +606 -0
- package/src/html/css/parse.ts +500 -0
- package/src/html/css/style.ts +1371 -0
- package/src/html/css/ua.ts +174 -0
- package/src/html/css/values.ts +358 -0
- package/src/html/dom.ts +410 -0
- package/src/html/hx.ts +55 -0
- package/src/html/index.ts +545 -0
- package/src/html/layout/block.ts +876 -0
- package/src/html/layout/boxes.ts +981 -0
- package/src/html/layout/flex.ts +297 -0
- package/src/html/layout/floats.ts +125 -0
- package/src/html/layout/inline.ts +870 -0
- package/src/html/layout/table.ts +380 -0
- package/src/html/node.ts +967 -0
- package/src/html/paint.ts +605 -0
- package/src/html/resources.ts +214 -0
- package/src/index.ts +638 -0
- package/src/internal/heights.ts +222 -0
- package/src/internal/hx.ts +53 -0
- package/src/internal/scroll.ts +288 -0
- package/src/internal/text.ts +44 -0
- package/src/internal/timers.ts +44 -0
- package/src/internal/widget.ts +92 -0
- package/src/markdown/ast.ts +163 -0
- package/src/markdown/hx.ts +51 -0
- package/src/markdown/index.ts +686 -0
- package/src/markdown/parse.ts +1037 -0
- package/src/markdown/spans.ts +143 -0
- package/src/media-player/backends.ts +155 -0
- package/src/media-player/control.ts +70 -0
- package/src/media-player/hx.ts +51 -0
- package/src/media-player/index.ts +378 -0
- package/src/media-player/mpv.ts +146 -0
- package/src/media-player/vlc.ts +97 -0
- package/src/richtext/index.ts +42 -0
- package/src/richtext/links.ts +78 -0
- package/src/richtext/menu.ts +49 -0
- package/src/richtext/node.ts +406 -0
- package/src/richtext/runs.ts +253 -0
- package/src/table/hx.ts +51 -0
- package/src/table/index.ts +1270 -0
- package/src/table/rows.ts +275 -0
- package/src/terminal/backends.ts +242 -0
- package/src/terminal/hx.ts +51 -0
- package/src/terminal/index.ts +574 -0
- package/src/terminal/title.ts +172 -0
- package/src/terminal/vt/colors.ts +247 -0
- package/src/terminal/vt/diff.ts +467 -0
- package/src/terminal/vt/fonts.ts +233 -0
- package/src/terminal/vt/index.ts +443 -0
- package/src/terminal/vt/keys.ts +369 -0
- package/src/terminal/vt/mouse.ts +144 -0
- package/src/terminal/vt/node.ts +1242 -0
- package/src/terminal/vt/pty.ts +334 -0
- package/src/terminal/vt/renderer.ts +555 -0
- package/src/terminal/vt/timers.ts +31 -0
- package/src/terminal/vt/xterm.ts +193 -0
- package/src/terminal-output/hx.ts +51 -0
- package/src/terminal-output/index.ts +340 -0
- package/src/terminal-output/runs.ts +79 -0
- package/src/three/canvas.ts +451 -0
- package/src/three/events.ts +197 -0
- package/src/three/geometries.ts +429 -0
- package/src/three/globals.ts +34 -0
- package/src/three/hx.ts +22 -0
- package/src/three/index.ts +98 -0
- package/src/three/jsx-dev-runtime.ts +27 -0
- package/src/three/jsx-runtime.ts +38 -0
- package/src/three/jsx.ts +275 -0
- package/src/three/materials.ts +270 -0
- package/src/three/math.ts +585 -0
- package/src/three/objects.ts +397 -0
- package/src/three/passes.ts +90 -0
- package/src/three/postprocess.ts +677 -0
- package/src/three/raycast.ts +189 -0
- package/src/three/react-reconciler.d.ts +50 -0
- package/src/three/reconciler.ts +624 -0
- package/src/three/renderer-direct.ts +1055 -0
- package/src/three/renderer-indirect.ts +570 -0
- package/src/three/store.ts +207 -0
- package/src/timeline/hx.ts +31 -0
- package/src/timeline/index.ts +603 -0
- package/src/tray-host/hx.ts +51 -0
- package/src/tray-host/index.ts +331 -0
- package/src/tray-host/manager.ts +639 -0
- package/src/tray-host/notify.ts +102 -0
- package/src/tray-host/protocol.ts +235 -0
- package/src/tree/hx.ts +51 -0
- package/src/tree/index.ts +1127 -0
- package/src/tree/internal.ts +87 -0
- package/src/tree/rows.ts +334 -0
- package/src/index.d.ts +0 -2
- package/src/index.js +0 -9
- package/src/sparkline/index.d.ts +0 -29
- package/src/sparkline/index.js +0 -50
- package/src/sparkline/node.js +0 -72
|
@@ -0,0 +1,1270 @@
|
|
|
1
|
+
// <Table> — the data table: columns as data, rows from the app's own
|
|
2
|
+
// objects, and only the rows in view actually built.
|
|
3
|
+
//
|
|
4
|
+
// This is a **successor to react-x11's own `<Table>`, not a wrapper around
|
|
5
|
+
// it** — the same relationship `<Tree>` has to core's tree. Core's may be
|
|
6
|
+
// stripped down or removed altogether; nothing here imports it, and the two
|
|
7
|
+
// share no code. What is kept is the behaviour a user has already learnt —
|
|
8
|
+
// select on the first click, open on the second, the header that stays put,
|
|
9
|
+
// the resize grips, the sort toggle — and the prop names core's call sites
|
|
10
|
+
// already use (`columns`, `rows`, `sort`, `selected`, `onActivate`,
|
|
11
|
+
// `onColumnResize`), so migrating is changing the import.
|
|
12
|
+
//
|
|
13
|
+
// What it grows, per docs/prd-table.md (the bar `<Tree>` set):
|
|
14
|
+
//
|
|
15
|
+
// 1. **The data is the app's.** `getId` plus per-column `value` render any
|
|
16
|
+
// row shape where it lies; a row of `{ id, … }` configures nothing.
|
|
17
|
+
// 2. **Rows may be any height.** `rowHeight` declares them uniform and the
|
|
18
|
+
// slice is arithmetic, core's model; omit it and the rows are measured —
|
|
19
|
+
// the tree's model, same machinery — so a `render` seam may wrap text or
|
|
20
|
+
// stack lines without breaking the scrollbar.
|
|
21
|
+
// 3. **Every visible part is a seam**: `column.render`,
|
|
22
|
+
// `column.renderHeader`, `renderRow`, `renderEmpty`, and a `styles` bag
|
|
23
|
+
// whose row/cell entries may be functions of row state.
|
|
24
|
+
// 4. **Ceremony is additive.** Sorting, selection, multi-selection,
|
|
25
|
+
// virtualization and the seams are independent opt-ins on this one
|
|
26
|
+
// element — the PRD's continuity contract. There is no second API.
|
|
27
|
+
//
|
|
28
|
+
// Nothing here registers a host element. A table is `<box>`, `<text>` and
|
|
29
|
+
// core's chevron, so there is no `registerElement` call, no JSX augmentation
|
|
30
|
+
// and no side effect at import time at all.
|
|
31
|
+
import React, {
|
|
32
|
+
useCallback,
|
|
33
|
+
useEffect,
|
|
34
|
+
useImperativeHandle,
|
|
35
|
+
useMemo,
|
|
36
|
+
useRef,
|
|
37
|
+
useState,
|
|
38
|
+
} from 'react';
|
|
39
|
+
import type { ReactElement, ReactNode, Ref } from 'react';
|
|
40
|
+
import { createStyles } from 'react-x11/style';
|
|
41
|
+
import type { StyleProp } from 'react-x11';
|
|
42
|
+
import { Icon, useDirection, useTheme } from 'react-x11';
|
|
43
|
+
import type {
|
|
44
|
+
DrawnNode,
|
|
45
|
+
KeyboardEvent,
|
|
46
|
+
MouseEvent,
|
|
47
|
+
ScrollableNode,
|
|
48
|
+
} from 'react-x11';
|
|
49
|
+
import {
|
|
50
|
+
XK_DOWN,
|
|
51
|
+
XK_END,
|
|
52
|
+
XK_HOME,
|
|
53
|
+
XK_LEFT,
|
|
54
|
+
XK_PAGE_DOWN,
|
|
55
|
+
XK_PAGE_UP,
|
|
56
|
+
XK_RETURN,
|
|
57
|
+
XK_RIGHT,
|
|
58
|
+
XK_UP,
|
|
59
|
+
} from 'react-x11/keysyms';
|
|
60
|
+
|
|
61
|
+
import { hx } from './hx.js';
|
|
62
|
+
import type { Host } from './hx.js';
|
|
63
|
+
// Shared with <Tree> — internal, deliberately not a shared *module*; the
|
|
64
|
+
// header of src/internal/heights.ts says why.
|
|
65
|
+
import { RowHeights } from '../internal/heights.js';
|
|
66
|
+
import { afterLayout, cancelAfterLayout } from '../internal/timers.js';
|
|
67
|
+
import { useReveal } from '../internal/scroll.js';
|
|
68
|
+
import {
|
|
69
|
+
MIN_COLUMN,
|
|
70
|
+
columnValue,
|
|
71
|
+
orderRows,
|
|
72
|
+
resolveGetId,
|
|
73
|
+
resolveWidths,
|
|
74
|
+
} from './rows.js';
|
|
75
|
+
import type {
|
|
76
|
+
TableCellState,
|
|
77
|
+
TableColumn,
|
|
78
|
+
TableHeaderCellState,
|
|
79
|
+
TableRow,
|
|
80
|
+
TableRowId,
|
|
81
|
+
TableRowState,
|
|
82
|
+
TableSort,
|
|
83
|
+
} from './rows.js';
|
|
84
|
+
|
|
85
|
+
// The row and column model comes out too: it is what the seams are handed,
|
|
86
|
+
// and an app that drives a table from outside — "what order are the rows in",
|
|
87
|
+
// "how wide did that column get" — does the same arithmetic.
|
|
88
|
+
export type {
|
|
89
|
+
ResolvedWidths,
|
|
90
|
+
TableCellState,
|
|
91
|
+
TableColumn,
|
|
92
|
+
TableHeaderCellState,
|
|
93
|
+
TableRow,
|
|
94
|
+
TableRowId,
|
|
95
|
+
TableRowState,
|
|
96
|
+
TableSort,
|
|
97
|
+
} from './rows.js';
|
|
98
|
+
export {
|
|
99
|
+
MIN_COLUMN,
|
|
100
|
+
UNSIZED_MIN,
|
|
101
|
+
columnValue,
|
|
102
|
+
defaultCompare,
|
|
103
|
+
orderRows,
|
|
104
|
+
resolveGetId,
|
|
105
|
+
resolveWidths,
|
|
106
|
+
} from './rows.js';
|
|
107
|
+
|
|
108
|
+
/** The header strip's height. Core's number — independent of the body's
|
|
109
|
+
* `rowHeight`, so a table of tall rows keeps a normal header. */
|
|
110
|
+
const HEADER_HEIGHT = 24;
|
|
111
|
+
/** The default row-height guess, and core's fixed row height. */
|
|
112
|
+
const ROW_HEIGHT = 24;
|
|
113
|
+
// The band you can grab, and the line you can see — two different sizes on
|
|
114
|
+
// purpose. A separator wants to be a hairline; a resize handle wants to be
|
|
115
|
+
// wide enough to hit without aiming. The band is centred **on** the rule:
|
|
116
|
+
// it is the boundary being moved, so a band lying to one side would light up
|
|
117
|
+
// off-centre and read as belonging to the column it covers.
|
|
118
|
+
const RULE = 1;
|
|
119
|
+
const HALF = 3;
|
|
120
|
+
const GRIP = HALF + RULE + HALF;
|
|
121
|
+
/** What one Left/Right on a focused grip is worth. */
|
|
122
|
+
const STEP = 16;
|
|
123
|
+
/** Rows kept either side of the viewport, so a fast scroll does not show a
|
|
124
|
+
* gap before the next frame catches up. */
|
|
125
|
+
const OVERSCAN = 6;
|
|
126
|
+
/**
|
|
127
|
+
* What to build before the viewport has been measured. `onViewport` cannot
|
|
128
|
+
* arrive until layout has run, which is a frame after the first commit, so
|
|
129
|
+
* there is always one render that has to guess — and guessing "all of them"
|
|
130
|
+
* puts a hundred thousand rows in the tree for a frame.
|
|
131
|
+
*/
|
|
132
|
+
const ASSUMED_ROWS = 40;
|
|
133
|
+
/**
|
|
134
|
+
* Where `virtual="auto"` starts virtualizing.
|
|
135
|
+
*
|
|
136
|
+
* Well above any viewport, so a table that is merely long is still built
|
|
137
|
+
* whole and keeps the one thing virtualization costs — every row in the
|
|
138
|
+
* accessibility tree. Well below the point where building every row is felt.
|
|
139
|
+
* The tree's threshold, for the tree's reasons.
|
|
140
|
+
*/
|
|
141
|
+
const VIRTUAL_THRESHOLD = 200;
|
|
142
|
+
/** The sort chevron, matched to the capitals of the 12px header caption. */
|
|
143
|
+
const SORT_MARK = 9;
|
|
144
|
+
|
|
145
|
+
const s = createStyles({
|
|
146
|
+
root: { flexGrow: 1, minHeight: 0, minWidth: 0 },
|
|
147
|
+
headerClip: { flexShrink: 0, overflow: 'hidden' },
|
|
148
|
+
headerRow: { flexDirection: 'row', flexShrink: 0 },
|
|
149
|
+
header: {
|
|
150
|
+
flexDirection: 'row',
|
|
151
|
+
alignItems: 'center',
|
|
152
|
+
height: HEADER_HEIGHT,
|
|
153
|
+
paddingStart: 8,
|
|
154
|
+
// Matching the leading inset: the sort mark sits against this edge, and
|
|
155
|
+
// without it the chevron touches the rule between the columns.
|
|
156
|
+
paddingEnd: 8,
|
|
157
|
+
flexShrink: 0,
|
|
158
|
+
transition: { backgroundColor: 80 },
|
|
159
|
+
},
|
|
160
|
+
headerLabel: {
|
|
161
|
+
fontSize: 12,
|
|
162
|
+
textWrap: 'nowrap',
|
|
163
|
+
textBoxTrim: 'cap-alphabetic',
|
|
164
|
+
},
|
|
165
|
+
/** Pushes the sort mark to the far end of the header, so the caption sits
|
|
166
|
+
* at the column's start edge — where the eye scans — and the marks of
|
|
167
|
+
* every sortable column line up instead of drifting with caption length. */
|
|
168
|
+
headerSpring: { flexGrow: 1 },
|
|
169
|
+
grip: {
|
|
170
|
+
flexShrink: 0,
|
|
171
|
+
cursor: 'col-resize',
|
|
172
|
+
alignSelf: 'stretch',
|
|
173
|
+
alignItems: 'center',
|
|
174
|
+
flexDirection: 'row',
|
|
175
|
+
justifyContent: 'center',
|
|
176
|
+
transition: { backgroundColor: 80 },
|
|
177
|
+
},
|
|
178
|
+
rule: { width: RULE, flexShrink: 0, alignSelf: 'stretch' },
|
|
179
|
+
body: { flexGrow: 1, minHeight: 0, overflow: 'scroll' },
|
|
180
|
+
rowsBox: { flexDirection: 'column', flexShrink: 0 },
|
|
181
|
+
row: {
|
|
182
|
+
flexDirection: 'row',
|
|
183
|
+
flexShrink: 0,
|
|
184
|
+
transition: { backgroundColor: 80 },
|
|
185
|
+
},
|
|
186
|
+
cell: {
|
|
187
|
+
justifyContent: 'center',
|
|
188
|
+
paddingStart: 8,
|
|
189
|
+
flexShrink: 0,
|
|
190
|
+
overflow: 'hidden',
|
|
191
|
+
},
|
|
192
|
+
cellText: { fontSize: 12, textWrap: 'nowrap', textBoxTrim: 'cap-alphabetic' },
|
|
193
|
+
spacer: { flexShrink: 0 },
|
|
194
|
+
sortMark: { marginStart: 4 },
|
|
195
|
+
empty: {
|
|
196
|
+
flexGrow: 1,
|
|
197
|
+
alignItems: 'center',
|
|
198
|
+
justifyContent: 'center',
|
|
199
|
+
padding: 16,
|
|
200
|
+
},
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
/** The selection that just changed, alongside the whole set. `id`/`row` name
|
|
204
|
+
* the row the gesture landed on; a select-all has no single row to name. */
|
|
205
|
+
export interface TableSelectChange<Row> {
|
|
206
|
+
type: 'replace' | 'toggle' | 'range' | 'all';
|
|
207
|
+
id?: TableRowId;
|
|
208
|
+
row?: Row;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/** The style overrides, one per part the table draws. Function forms are
|
|
212
|
+
* called per rendered row or cell, so a style can follow row state — zebra
|
|
213
|
+
* striping is `row: (s) => s.index % 2 === 1 && { backgroundColor: … }` —
|
|
214
|
+
* without a render prop that repaints everything. */
|
|
215
|
+
export interface TableStyles<Row> {
|
|
216
|
+
/** The header strip — the clipping box the header row scrolls inside. */
|
|
217
|
+
header?: StyleProp;
|
|
218
|
+
headerCell?: StyleProp | ((state: TableHeaderCellState<Row>) => StyleProp);
|
|
219
|
+
row?: StyleProp | ((state: TableRowState<Row>) => StyleProp);
|
|
220
|
+
cell?: StyleProp | ((state: TableCellState<Row>) => StyleProp);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* A `<Table>`'s imperative side.
|
|
225
|
+
*
|
|
226
|
+
* `handleKey` is the one worth knowing about: a filter box above a table
|
|
227
|
+
* holds the keyboard, and forwarding the arrows to this is how the list
|
|
228
|
+
* below it still walks. It reports whether the table took the key, so the
|
|
229
|
+
* caller keeps whatever it did not.
|
|
230
|
+
*/
|
|
231
|
+
export interface TableHandle<Row = any> {
|
|
232
|
+
focus: () => void;
|
|
233
|
+
/** Select a row by id, as a click would; `null` clears an uncontrolled
|
|
234
|
+
* selection. An id naming no row is a no-op. */
|
|
235
|
+
select: (id: TableRowId | null) => void;
|
|
236
|
+
/** Bring a row into view. `false` when the id names nothing. */
|
|
237
|
+
scrollToRow: (id: TableRowId) => boolean;
|
|
238
|
+
handleKey: (ev: KeyboardEvent) => boolean;
|
|
239
|
+
/** The rows in display order — the sorted model, not the rendered slice. */
|
|
240
|
+
rows: () => readonly TableRow<Row>[];
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
type BoxProps = Host['box'];
|
|
244
|
+
|
|
245
|
+
interface TableBaseProps<Row> extends Omit<
|
|
246
|
+
BoxProps,
|
|
247
|
+
'style' | 'children' | 'ref' | 'onKeyDown'
|
|
248
|
+
> {
|
|
249
|
+
columns?: readonly TableColumn<Row>[];
|
|
250
|
+
rows?: readonly Row[];
|
|
251
|
+
/** The row's key. Defaults to `row.id`, and a row that resolves no id is a
|
|
252
|
+
* remedial `TypeError` rather than a broken render. Memoize it. */
|
|
253
|
+
getId?: (row: Row) => TableRowId;
|
|
254
|
+
|
|
255
|
+
/** Controlled sort **descriptor**. Who *orders the rows* is a separate
|
|
256
|
+
* question — see {@link presorted}. */
|
|
257
|
+
sort?: TableSort | null;
|
|
258
|
+
defaultSort?: TableSort | null;
|
|
259
|
+
onSortChange?: (sort: TableSort | null) => void;
|
|
260
|
+
/**
|
|
261
|
+
* The rows already arrive in display order — a server sorted them — so the
|
|
262
|
+
* table only renders the sort indicator and reports toggles. Composes with
|
|
263
|
+
* either descriptor mode; without it the table orders the rows itself,
|
|
264
|
+
* `column.compare ?? natural order over value()`.
|
|
265
|
+
*/
|
|
266
|
+
presorted?: boolean;
|
|
267
|
+
|
|
268
|
+
/** The *open* gesture on top of selection — a double click, or Enter. */
|
|
269
|
+
onActivate?: (id: TableRowId, row: Row) => void;
|
|
270
|
+
/** Right-click on a row. The row is selected first unless it is already
|
|
271
|
+
* part of the selection — the file-manager convention, so a context menu
|
|
272
|
+
* always applies to what is under the pointer. */
|
|
273
|
+
onRowContextMenu?: (id: TableRowId, row: Row, ev: MouseEvent) => void;
|
|
274
|
+
/** A column was resized — by drag, or by Left/Right on the focused grip.
|
|
275
|
+
* The resize also converts the column to fixed at that width. */
|
|
276
|
+
onColumnResize?: (id: string, width: number) => void;
|
|
277
|
+
|
|
278
|
+
/**
|
|
279
|
+
* Declare every row exactly this tall, in pixels — core's model: the
|
|
280
|
+
* visible slice is arithmetic and nothing is ever measured. **Omit it and
|
|
281
|
+
* rows are measured instead**: any row may be any height, at the cost of
|
|
282
|
+
* the measure pass. `rowHeight={24}` restores core's exact behaviour.
|
|
283
|
+
*/
|
|
284
|
+
rowHeight?: number;
|
|
285
|
+
/** What an unmeasured row is assumed — and floored — at, while measuring.
|
|
286
|
+
* Default 24. The scrollbar is this guess for every row not yet seen, and
|
|
287
|
+
* it converges as you scroll. */
|
|
288
|
+
estimatedRowHeight?: number;
|
|
289
|
+
/** Build only the rows on screen. `'auto'` (the default) turns it on past
|
|
290
|
+
* 200 rows. */
|
|
291
|
+
virtual?: boolean | 'auto';
|
|
292
|
+
/** Rows built either side of the viewport. */
|
|
293
|
+
overscan?: number;
|
|
294
|
+
|
|
295
|
+
/**
|
|
296
|
+
* Everything inside the row box, given what would have been there.
|
|
297
|
+
*
|
|
298
|
+
* The row *box* stays this component's — it carries the height
|
|
299
|
+
* virtualization counts on, the role and aria the table is read through,
|
|
300
|
+
* and the click that selects. Style it with `styles.row`.
|
|
301
|
+
*/
|
|
302
|
+
renderRow?: (state: TableRowState<Row>, content: ReactNode[]) => ReactNode;
|
|
303
|
+
/** The body's content when the rows resolve empty. Nothing by default —
|
|
304
|
+
* the header still shows. */
|
|
305
|
+
renderEmpty?: () => ReactNode;
|
|
306
|
+
|
|
307
|
+
styles?: TableStyles<Row>;
|
|
308
|
+
style?: StyleProp;
|
|
309
|
+
/** Whether the table is a tab stop. Default true; `false` for a table
|
|
310
|
+
* inside a popup that owns the focus. */
|
|
311
|
+
focusable?: boolean;
|
|
312
|
+
ref?: Ref<TableHandle<Row>>;
|
|
313
|
+
/** react-x11's queries read this off `props` at run time; its element
|
|
314
|
+
* declarations do not carry it, so it is declared here and reaches the
|
|
315
|
+
* root box through the rest spread. */
|
|
316
|
+
'data-testname'?: string;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
/** Single selection — the default, and core's behaviour. */
|
|
320
|
+
export interface TableSingleSelectProps<Row> extends TableBaseProps<Row> {
|
|
321
|
+
selectionMode?: 'single';
|
|
322
|
+
selected?: TableRowId | null;
|
|
323
|
+
defaultSelected?: TableRowId | null;
|
|
324
|
+
onSelect?: (id: TableRowId, row: Row) => void;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
/** Multiple selection: the file-manager grammar — click replaces, Ctrl
|
|
328
|
+
* toggles, Shift extends from the anchor, Ctrl+A takes everything. */
|
|
329
|
+
export interface TableMultiSelectProps<Row> extends TableBaseProps<Row> {
|
|
330
|
+
selectionMode: 'multiple';
|
|
331
|
+
selected?: readonly TableRowId[];
|
|
332
|
+
defaultSelected?: readonly TableRowId[];
|
|
333
|
+
onSelectedChange?: (
|
|
334
|
+
selected: TableRowId[],
|
|
335
|
+
change: TableSelectChange<Row>,
|
|
336
|
+
) => void;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
/** Display only: no cursor, no hover, clicks select nothing. */
|
|
340
|
+
export interface TableStaticProps<Row> extends TableBaseProps<Row> {
|
|
341
|
+
selectionMode: 'none';
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
/**
|
|
345
|
+
* The props are a discriminated union on `selectionMode`, the Calendar's
|
|
346
|
+
* precedent: one id and an array of ids are different shapes, and the
|
|
347
|
+
* compiler walking the caller through the change is the point. The wrong
|
|
348
|
+
* pairing at run time is a remedial `TypeError`, not an empty render.
|
|
349
|
+
*/
|
|
350
|
+
export type TableProps<Row = any> =
|
|
351
|
+
| TableSingleSelectProps<Row>
|
|
352
|
+
| TableMultiSelectProps<Row>
|
|
353
|
+
| TableStaticProps<Row>;
|
|
354
|
+
|
|
355
|
+
/** Every prop of every variant, loosened — what the implementation reads
|
|
356
|
+
* after the runtime check has vouched for the pairing. */
|
|
357
|
+
interface TableAllProps<Row> extends TableBaseProps<Row> {
|
|
358
|
+
selectionMode?: 'none' | 'single' | 'multiple';
|
|
359
|
+
selected?: TableRowId | null | readonly TableRowId[];
|
|
360
|
+
defaultSelected?: TableRowId | null | readonly TableRowId[];
|
|
361
|
+
onSelect?: (id: TableRowId, row: Row) => void;
|
|
362
|
+
onSelectedChange?: (
|
|
363
|
+
selected: TableRowId[],
|
|
364
|
+
change: TableSelectChange<Row>,
|
|
365
|
+
) => void;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
const EMPTY_SET: ReadonlySet<TableRowId> = new Set();
|
|
369
|
+
|
|
370
|
+
/**
|
|
371
|
+
* `<Table columns rows />` — a data table with a header that stays put.
|
|
372
|
+
*
|
|
373
|
+
* <Table
|
|
374
|
+
* columns={[{ id: 'name', label: 'Name', flex: 1 }]}
|
|
375
|
+
* rows={[{ id: 1, name: 'index.ts' }]}
|
|
376
|
+
* />
|
|
377
|
+
*
|
|
378
|
+
* Columns are `{ id, label, width | flex, align, value, render }` — core's
|
|
379
|
+
* shape. `value(row)` feeds sorting and the default cell text;
|
|
380
|
+
* `render(row, state)` replaces the cell entirely, and the state says
|
|
381
|
+
* whether it is on the selected row, because that row is a filled bar and a
|
|
382
|
+
* colour chosen against the resting background is unreadable on it. A
|
|
383
|
+
* column declaring neither `width` nor `flex` stretches (`flex: 1`, floored
|
|
384
|
+
* at 120px), so a table with no sizing config fills its box.
|
|
385
|
+
*
|
|
386
|
+
* Sorting is uncontrolled unless you pass `sort`; the header reports
|
|
387
|
+
* `onSortChange` either way, and `presorted` says the rows already arrive
|
|
388
|
+
* ordered. Selection is single by default (`selected` + `onSelect`),
|
|
389
|
+
* `selectionMode="multiple"` for the set (`selected` + `onSelectedChange`),
|
|
390
|
+
* `"none"` for display. `onActivate` is the *open* gesture on top — a double
|
|
391
|
+
* click or Enter.
|
|
392
|
+
*
|
|
393
|
+
* The table is a single tab stop; Up/Down walk the rows, PageUp/PageDown
|
|
394
|
+
* move by a viewport, Home/End jump, Shift extends in multiple mode, Space
|
|
395
|
+
* toggles there, Ctrl+A takes everything. **The focus is on the table, not
|
|
396
|
+
* the row** — a virtualized row is unmounted the moment it scrolls out and
|
|
397
|
+
* focus would go with it, so the selection is the cursor. The honest caveat:
|
|
398
|
+
* while virtualizing, only the rendered rows are in the accessibility tree —
|
|
399
|
+
* the same rows a sighted user can see.
|
|
400
|
+
*/
|
|
401
|
+
export function Table<Row = any>(props: TableProps<Row>): ReactElement {
|
|
402
|
+
const {
|
|
403
|
+
columns = [],
|
|
404
|
+
rows = [],
|
|
405
|
+
getId,
|
|
406
|
+
sort,
|
|
407
|
+
defaultSort,
|
|
408
|
+
onSortChange,
|
|
409
|
+
presorted = false,
|
|
410
|
+
onActivate,
|
|
411
|
+
onRowContextMenu,
|
|
412
|
+
onColumnResize,
|
|
413
|
+
rowHeight,
|
|
414
|
+
estimatedRowHeight,
|
|
415
|
+
virtual = 'auto',
|
|
416
|
+
overscan = OVERSCAN,
|
|
417
|
+
renderRow,
|
|
418
|
+
renderEmpty,
|
|
419
|
+
styles,
|
|
420
|
+
style,
|
|
421
|
+
focusable = true,
|
|
422
|
+
ref,
|
|
423
|
+
selectionMode = 'single',
|
|
424
|
+
selected,
|
|
425
|
+
defaultSelected,
|
|
426
|
+
onSelect,
|
|
427
|
+
onSelectedChange,
|
|
428
|
+
// ours to chain rather than to hand over: virtualization and the sticky
|
|
429
|
+
// header are measured through both of these
|
|
430
|
+
onScroll,
|
|
431
|
+
onViewport,
|
|
432
|
+
...boxProps
|
|
433
|
+
} = props as TableAllProps<Row>;
|
|
434
|
+
|
|
435
|
+
// The union keeps a caller honest at compile time; this keeps a caller
|
|
436
|
+
// honest who got the props from somewhere the compiler could not see.
|
|
437
|
+
if (selectionMode === 'multiple') {
|
|
438
|
+
if (selected !== undefined && !Array.isArray(selected)) {
|
|
439
|
+
throw new TypeError(
|
|
440
|
+
'<Table selectionMode="multiple"> takes `selected` as an array of ' +
|
|
441
|
+
'ids. Pass `selected={[id]}`, or drop selectionMode for single ' +
|
|
442
|
+
'selection.',
|
|
443
|
+
);
|
|
444
|
+
}
|
|
445
|
+
if (defaultSelected !== undefined && !Array.isArray(defaultSelected)) {
|
|
446
|
+
throw new TypeError(
|
|
447
|
+
'<Table selectionMode="multiple"> takes `defaultSelected` as an ' +
|
|
448
|
+
'array of ids.',
|
|
449
|
+
);
|
|
450
|
+
}
|
|
451
|
+
} else if (Array.isArray(selected) || Array.isArray(defaultSelected)) {
|
|
452
|
+
throw new TypeError(
|
|
453
|
+
`<Table> got an array for \`selected\` but selectionMode is ` +
|
|
454
|
+
`'${selectionMode}' — pass selectionMode="multiple", or one id.`,
|
|
455
|
+
);
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
const theme = useTheme();
|
|
459
|
+
const rtl = useDirection() === 'rtl';
|
|
460
|
+
const [ownSort, setOwnSort] = useState<TableSort | null>(defaultSort ?? null);
|
|
461
|
+
const [ownSingle, setOwnSingle] = useState<TableRowId | null>(() =>
|
|
462
|
+
Array.isArray(defaultSelected)
|
|
463
|
+
? null
|
|
464
|
+
: ((defaultSelected as TableRowId | null | undefined) ?? null),
|
|
465
|
+
);
|
|
466
|
+
const [ownMulti, setOwnMulti] = useState<readonly TableRowId[]>(() =>
|
|
467
|
+
Array.isArray(defaultSelected) ? defaultSelected : [],
|
|
468
|
+
);
|
|
469
|
+
/** The keyboard cursor in multiple mode; in single mode the selection is
|
|
470
|
+
* the cursor. */
|
|
471
|
+
const [cursorId, setCursorId] = useState<TableRowId | null>(null);
|
|
472
|
+
const [userWidths, setUserWidths] = useState<
|
|
473
|
+
Readonly<Record<string, number>>
|
|
474
|
+
>({});
|
|
475
|
+
const [scrollX, setScrollX] = useState(0);
|
|
476
|
+
const [view, setView] = useState({ top: 0, height: 0, width: 0 });
|
|
477
|
+
// Bumped by a measurement pass that found a row taller or shorter than the
|
|
478
|
+
// index believed. It is the only reason the component re-renders for a
|
|
479
|
+
// measurement, and a pass that finds nothing new does not bump it — which
|
|
480
|
+
// is what makes measure → render → measure converge instead of spinning.
|
|
481
|
+
const [, setMeasured] = useState(0);
|
|
482
|
+
|
|
483
|
+
const readId = useMemo(() => resolveGetId<Row>(getId), [getId]);
|
|
484
|
+
const activeSort = sort === undefined ? ownSort : sort;
|
|
485
|
+
|
|
486
|
+
const ordered = useMemo(
|
|
487
|
+
() => orderRows(rows, columns, readId, activeSort, presorted),
|
|
488
|
+
[rows, columns, readId, activeSort, presorted],
|
|
489
|
+
);
|
|
490
|
+
|
|
491
|
+
const selectedIds: ReadonlySet<TableRowId> = useMemo(() => {
|
|
492
|
+
if (selectionMode === 'none') return EMPTY_SET;
|
|
493
|
+
if (selectionMode === 'multiple') {
|
|
494
|
+
const list =
|
|
495
|
+
selected === undefined ? ownMulti : (selected as readonly TableRowId[]);
|
|
496
|
+
return new Set(list);
|
|
497
|
+
}
|
|
498
|
+
const one =
|
|
499
|
+
selected === undefined ? ownSingle : (selected as TableRowId | null);
|
|
500
|
+
return one === null || one === undefined ? EMPTY_SET : new Set([one]);
|
|
501
|
+
}, [selectionMode, selected, ownMulti, ownSingle]);
|
|
502
|
+
|
|
503
|
+
const cursor =
|
|
504
|
+
selectionMode === 'multiple'
|
|
505
|
+
? cursorId
|
|
506
|
+
: selected === undefined
|
|
507
|
+
? ownSingle
|
|
508
|
+
: (selected as TableRowId | null);
|
|
509
|
+
|
|
510
|
+
// Held-down or fast keys arrive in a burst, and every handler in that
|
|
511
|
+
// burst sees the render it started from — so the order, the selection and
|
|
512
|
+
// the cursor are mirrored here and updated the moment they change, or
|
|
513
|
+
// three Downs in a row all step off the same starting point.
|
|
514
|
+
const orderedRef = useRef(ordered);
|
|
515
|
+
orderedRef.current = ordered;
|
|
516
|
+
const columnsRef = useRef(columns);
|
|
517
|
+
columnsRef.current = columns;
|
|
518
|
+
const selectedRef = useRef(selectedIds);
|
|
519
|
+
selectedRef.current = selectedIds;
|
|
520
|
+
const cursorRef = useRef(cursor);
|
|
521
|
+
cursorRef.current = cursor;
|
|
522
|
+
const viewRef = useRef(view);
|
|
523
|
+
viewRef.current = view;
|
|
524
|
+
/** Where a Shift range grows from — the last plain click or plain step. */
|
|
525
|
+
const anchorRef = useRef<TableRowId | null>(null);
|
|
526
|
+
|
|
527
|
+
const root = useRef<DrawnNode | null>(null);
|
|
528
|
+
const body = useRef<ScrollableNode | null>(null);
|
|
529
|
+
/** The rows on screen, by id — with the index each was drawn at, so a
|
|
530
|
+
* measurement pass does not have to search the row list for it. */
|
|
531
|
+
const rowNodes = useRef(
|
|
532
|
+
new Map<TableRowId, { node: DrawnNode; at: number }>(),
|
|
533
|
+
);
|
|
534
|
+
const drag = useRef<{ id: string; from: number; width: number } | null>(null);
|
|
535
|
+
const userWidthsRef = useRef(userWidths);
|
|
536
|
+
userWidthsRef.current = userWidths;
|
|
537
|
+
|
|
538
|
+
/** Declared uniform: divide, never measure — core's model. */
|
|
539
|
+
const uniform = rowHeight !== undefined;
|
|
540
|
+
const estimate = rowHeight ?? estimatedRowHeight ?? ROW_HEIGHT;
|
|
541
|
+
// `useState` for its lazy initializer rather than `useRef`: one index per
|
|
542
|
+
// mounted table, built once. Nothing ever calls the setter — what it holds
|
|
543
|
+
// is mutable and its changes are announced through `setMeasured`. With
|
|
544
|
+
// `rowHeight` declared nothing is ever measured, every height stays the
|
|
545
|
+
// estimate, and the index computes exactly `i * rowHeight` — one code
|
|
546
|
+
// path for both models.
|
|
547
|
+
const [heights] = useState(() => new RowHeights(estimate));
|
|
548
|
+
heights.sync(ordered, estimate);
|
|
549
|
+
|
|
550
|
+
const virtualizing =
|
|
551
|
+
virtual === true ||
|
|
552
|
+
(virtual === 'auto' && ordered.length > VIRTUAL_THRESHOLD);
|
|
553
|
+
|
|
554
|
+
// The slice worth building: what is on screen, plus a little either side.
|
|
555
|
+
const first = virtualizing
|
|
556
|
+
? Math.max(0, heights.indexAt(view.top) - overscan)
|
|
557
|
+
: 0;
|
|
558
|
+
let last = ordered.length;
|
|
559
|
+
if (virtualizing) {
|
|
560
|
+
if (view.height > 0) {
|
|
561
|
+
last = Math.min(
|
|
562
|
+
ordered.length,
|
|
563
|
+
heights.indexAt(view.top + view.height) + 1 + overscan,
|
|
564
|
+
);
|
|
565
|
+
} else {
|
|
566
|
+
last = Math.min(ordered.length, first + ASSUMED_ROWS);
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
/** Where the slice starts, and how much of the list is below it — the two
|
|
570
|
+
* spacers that keep the scrollbar measuring the whole table. */
|
|
571
|
+
const above = virtualizing ? heights.offsetAt(first) : 0;
|
|
572
|
+
const below = virtualizing ? heights.total() - heights.offsetAt(last) : 0;
|
|
573
|
+
|
|
574
|
+
/** Columns resolve to pixels once, at the table level, per (columns,
|
|
575
|
+
* viewport, resizes) — every row agrees on the grid by construction. */
|
|
576
|
+
const { widths, total } = useMemo(
|
|
577
|
+
() => resolveWidths(columns, userWidths, view.width),
|
|
578
|
+
[columns, userWidths, view.width],
|
|
579
|
+
);
|
|
580
|
+
|
|
581
|
+
/**
|
|
582
|
+
* The scroll the table owes a row, and the pane's real offset read back
|
|
583
|
+
* after every layout — the two halves of `../internal/scroll.ts`, which
|
|
584
|
+
* says why a reveal cannot be a one-shot and why `onScroll` is not the
|
|
585
|
+
* whole story.
|
|
586
|
+
*/
|
|
587
|
+
const reveal = useReveal({
|
|
588
|
+
box: body,
|
|
589
|
+
rows: orderedRef,
|
|
590
|
+
nodes: rowNodes,
|
|
591
|
+
heights,
|
|
592
|
+
});
|
|
593
|
+
|
|
594
|
+
/**
|
|
595
|
+
* Re-read the offset the body is *actually* at.
|
|
596
|
+
*
|
|
597
|
+
* The pane moves silently — it resolves a queued reveal during layout, and
|
|
598
|
+
* re-clamps an offset the content outgrew or outshrank — and a slice built
|
|
599
|
+
* from the offset before those is drawn where the viewport is not: a blank
|
|
600
|
+
* band where the rows should be, and no way back until a scroll of your own
|
|
601
|
+
* re-syncs it by accident.
|
|
602
|
+
*/
|
|
603
|
+
const syncScroll = useCallback((): void => {
|
|
604
|
+
const box = body.current;
|
|
605
|
+
if (!box) return;
|
|
606
|
+
const { scrollX: x, scrollY: y } = box;
|
|
607
|
+
setScrollX((prev) => (prev === x ? prev : x));
|
|
608
|
+
// Only a virtualizing table reads the vertical offset — a whole one has
|
|
609
|
+
// no slice to rebuild, and re-rendering it on a scroll it already drew
|
|
610
|
+
// would be work for nothing.
|
|
611
|
+
if (virtualizing)
|
|
612
|
+
setView((prev) => (prev.top === y ? prev : { ...prev, top: y }));
|
|
613
|
+
}, [virtualizing]);
|
|
614
|
+
|
|
615
|
+
/**
|
|
616
|
+
* Read back what the rows on screen actually laid out at.
|
|
617
|
+
*
|
|
618
|
+
* Runs a tick after the commit because layout runs on the frame flush —
|
|
619
|
+
* an effect reads the *previous* pass's geometry (see ./timers.ts).
|
|
620
|
+
* Idempotent: a row whose measurement has not changed reports no change,
|
|
621
|
+
* so the second pass over the same rows re-renders nothing. Rows above
|
|
622
|
+
* the viewport anchor shift the scroll offset by their delta, or
|
|
623
|
+
* measuring a row already scrolled past yanks the list under the pointer.
|
|
624
|
+
*/
|
|
625
|
+
const measureRows = useCallback((): boolean => {
|
|
626
|
+
if (uniform || !virtualizing) return false;
|
|
627
|
+
// Before the first `onViewport` the flex columns sit on their floors and
|
|
628
|
+
// every row is laid out against a width that is about to change — there
|
|
629
|
+
// is nothing honest to measure yet.
|
|
630
|
+
if (viewRef.current.width <= 0) return false;
|
|
631
|
+
// A row laid out at a width the columns no longer resolve to is a
|
|
632
|
+
// measurement of the wrong table, and it must not be recorded — a row
|
|
633
|
+
// that scrolls out before the corrected pass would keep a wrong-width
|
|
634
|
+
// height forever. The expectation is recomputed from event-fresh inputs
|
|
635
|
+
// rather than read from the last render: this tick runs *between* an
|
|
636
|
+
// `onViewport` and the re-render it causes, which is exactly when the
|
|
637
|
+
// rendered state is stale. The tree never needs any of this: its row
|
|
638
|
+
// width never depends on a resolved column model.
|
|
639
|
+
const expected = resolveWidths(
|
|
640
|
+
columnsRef.current,
|
|
641
|
+
userWidthsRef.current,
|
|
642
|
+
viewRef.current.width,
|
|
643
|
+
).total;
|
|
644
|
+
const box = body.current;
|
|
645
|
+
const rows = orderedRef.current;
|
|
646
|
+
const anchor = box ? heights.indexAt(box.scrollY) : 0;
|
|
647
|
+
let shift = 0;
|
|
648
|
+
let changed = false;
|
|
649
|
+
for (const [id, { node, at }] of rowNodes.current) {
|
|
650
|
+
if (rows[at]?.id !== id) continue; // drawn against a list that moved
|
|
651
|
+
if (node.abs.width !== expected) continue;
|
|
652
|
+
const height = node.abs.height;
|
|
653
|
+
const was = heights.heightAt(at);
|
|
654
|
+
if (!heights.measure(id, at, height)) continue;
|
|
655
|
+
changed = true;
|
|
656
|
+
if (at < anchor) shift += height - was;
|
|
657
|
+
}
|
|
658
|
+
if (!changed) return false;
|
|
659
|
+
if (shift !== 0 && box) {
|
|
660
|
+
reveal.scrollTo(box.scrollY + shift);
|
|
661
|
+
}
|
|
662
|
+
setMeasured((n) => n + 1);
|
|
663
|
+
return true;
|
|
664
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps -- `heights` is a
|
|
665
|
+
// stable instance
|
|
666
|
+
}, [uniform, virtualizing]);
|
|
667
|
+
|
|
668
|
+
/**
|
|
669
|
+
* The one tick after layout, and everything that can only be known there:
|
|
670
|
+
* what the rows measured, whether an owed scroll can go further now that
|
|
671
|
+
* the new rows are laid out, and where the body actually ended up. In that
|
|
672
|
+
* order — each step can move the offset the next one reads.
|
|
673
|
+
*
|
|
674
|
+
* Scheduled for every render a virtualized table makes, because every one
|
|
675
|
+
* of them can move the offset its next slice is built from. A whole table
|
|
676
|
+
* needs none of it: `onViewport` is when its content can have been
|
|
677
|
+
* re-clamped, and it rebuilds no slice anyway.
|
|
678
|
+
*/
|
|
679
|
+
useEffect(() => {
|
|
680
|
+
if (!virtualizing) return undefined;
|
|
681
|
+
const id = afterLayout(() => {
|
|
682
|
+
// `measureRows` first, and its answer handed on: a pass that moved the
|
|
683
|
+
// heights has not settled anything, and an owed scroll judged against
|
|
684
|
+
// the layout it is about to invalidate is not owed any less.
|
|
685
|
+
reveal.retry(measureRows());
|
|
686
|
+
syncScroll();
|
|
687
|
+
});
|
|
688
|
+
return () => cancelAfterLayout(id);
|
|
689
|
+
});
|
|
690
|
+
|
|
691
|
+
/** Put a row in view, by the index its call site already has. */
|
|
692
|
+
const revealAt = useCallback(
|
|
693
|
+
(at: number): void => {
|
|
694
|
+
const row = orderedRef.current[at];
|
|
695
|
+
if (row) reveal.to(row.id);
|
|
696
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps -- `reveal` is a
|
|
697
|
+
// stable handle
|
|
698
|
+
},
|
|
699
|
+
[reveal],
|
|
700
|
+
);
|
|
701
|
+
|
|
702
|
+
const commitSingle = useCallback(
|
|
703
|
+
(row: TableRow<Row>): void => {
|
|
704
|
+
cursorRef.current = row.id;
|
|
705
|
+
if (selected === undefined) setOwnSingle(row.id);
|
|
706
|
+
onSelect?.(row.id, row.row);
|
|
707
|
+
},
|
|
708
|
+
[selected, onSelect],
|
|
709
|
+
);
|
|
710
|
+
|
|
711
|
+
const commitMulti = useCallback(
|
|
712
|
+
(ids: TableRowId[], change: TableSelectChange<Row>): void => {
|
|
713
|
+
selectedRef.current = new Set(ids);
|
|
714
|
+
if (selected === undefined) setOwnMulti(ids);
|
|
715
|
+
onSelectedChange?.(ids, change);
|
|
716
|
+
},
|
|
717
|
+
[selected, onSelectedChange],
|
|
718
|
+
);
|
|
719
|
+
|
|
720
|
+
/**
|
|
721
|
+
* One gesture, pointer or keyboard: land on a row, with modifiers.
|
|
722
|
+
* Multiple mode is the file-manager grammar — plain replaces and moves the
|
|
723
|
+
* anchor, Ctrl toggles, Shift extends from the anchor. The cursor and the
|
|
724
|
+
* anchor are **ids, not indexes**, so a re-sort moves the rows, not the
|
|
725
|
+
* user's place.
|
|
726
|
+
*/
|
|
727
|
+
const tap = useCallback(
|
|
728
|
+
(row: TableRow<Row>, mods: { ctrl: boolean; shift: boolean }): void => {
|
|
729
|
+
if (selectionMode === 'none') return;
|
|
730
|
+
if (selectionMode === 'single') {
|
|
731
|
+
commitSingle(row);
|
|
732
|
+
revealAt(row.index);
|
|
733
|
+
return;
|
|
734
|
+
}
|
|
735
|
+
cursorRef.current = row.id;
|
|
736
|
+
setCursorId(row.id);
|
|
737
|
+
const anchor = anchorRef.current;
|
|
738
|
+
if (mods.shift && anchor !== null) {
|
|
739
|
+
const all = orderedRef.current;
|
|
740
|
+
let a = all.findIndex((r) => r.id === anchor);
|
|
741
|
+
let b = row.index;
|
|
742
|
+
if (a < 0) a = b;
|
|
743
|
+
const [lo, hi] = a <= b ? [a, b] : [b, a];
|
|
744
|
+
commitMulti(
|
|
745
|
+
all.slice(lo, hi + 1).map((r) => r.id),
|
|
746
|
+
{ type: 'range', id: row.id, row: row.row },
|
|
747
|
+
);
|
|
748
|
+
} else if (mods.ctrl) {
|
|
749
|
+
const next = new Set(selectedRef.current);
|
|
750
|
+
if (next.has(row.id)) next.delete(row.id);
|
|
751
|
+
else next.add(row.id);
|
|
752
|
+
anchorRef.current = row.id;
|
|
753
|
+
commitMulti([...next], { type: 'toggle', id: row.id, row: row.row });
|
|
754
|
+
} else {
|
|
755
|
+
anchorRef.current = row.id;
|
|
756
|
+
commitMulti([row.id], { type: 'replace', id: row.id, row: row.row });
|
|
757
|
+
}
|
|
758
|
+
revealAt(row.index);
|
|
759
|
+
},
|
|
760
|
+
[selectionMode, commitSingle, commitMulti, revealAt],
|
|
761
|
+
);
|
|
762
|
+
|
|
763
|
+
const activate = useCallback(
|
|
764
|
+
(row: TableRow<Row>): void => {
|
|
765
|
+
onActivate?.(row.id, row.row);
|
|
766
|
+
},
|
|
767
|
+
[onActivate],
|
|
768
|
+
);
|
|
769
|
+
|
|
770
|
+
const toggleSort = (column: TableColumn<Row>): void => {
|
|
771
|
+
const next: TableSort =
|
|
772
|
+
activeSort?.column === column.id && activeSort.direction === 'asc'
|
|
773
|
+
? { column: column.id, direction: 'desc' }
|
|
774
|
+
: { column: column.id, direction: 'asc' };
|
|
775
|
+
if (sort === undefined) setOwnSort(next);
|
|
776
|
+
onSortChange?.(next);
|
|
777
|
+
};
|
|
778
|
+
|
|
779
|
+
const resize = (column: TableColumn<Row>, width: number): void => {
|
|
780
|
+
const next = Math.max(column.minWidth ?? MIN_COLUMN, Math.round(width));
|
|
781
|
+
if (userWidthsRef.current[column.id] === next) return;
|
|
782
|
+
const map = { ...userWidthsRef.current, [column.id]: next };
|
|
783
|
+
userWidthsRef.current = map;
|
|
784
|
+
setUserWidths(map);
|
|
785
|
+
onColumnResize?.(column.id, next);
|
|
786
|
+
};
|
|
787
|
+
|
|
788
|
+
const resizeProps = (column: TableColumn<Row>, resolved: number) => ({
|
|
789
|
+
// The handle takes focus on the press, so it is in the tab order whether
|
|
790
|
+
// or not it answers a key. Left/Right, the pair `SplitPane`'s divider
|
|
791
|
+
// takes; the table's own keys are Up/Down and never collide.
|
|
792
|
+
focusable: true,
|
|
793
|
+
onMouseDown: (ev: MouseEvent) => {
|
|
794
|
+
drag.current = { id: column.id, from: ev.x, width: resolved };
|
|
795
|
+
ev.capturePointer();
|
|
796
|
+
},
|
|
797
|
+
onMouseMove: (ev: MouseEvent) => {
|
|
798
|
+
const d = drag.current;
|
|
799
|
+
if (!d || d.id !== column.id) return;
|
|
800
|
+
// dragging the grip away from the column's own start edge widens it,
|
|
801
|
+
// and which way that is follows the layout
|
|
802
|
+
resize(column, d.width + (rtl ? d.from - ev.x : ev.x - d.from));
|
|
803
|
+
},
|
|
804
|
+
onMouseUp: () => {
|
|
805
|
+
drag.current = null;
|
|
806
|
+
},
|
|
807
|
+
onKeyDown: (ev: KeyboardEvent) => {
|
|
808
|
+
const narrower = rtl ? XK_RIGHT : XK_LEFT;
|
|
809
|
+
const wider = rtl ? XK_LEFT : XK_RIGHT;
|
|
810
|
+
if (ev.keysym === narrower) resize(column, resolved - STEP);
|
|
811
|
+
else if (ev.keysym === wider) resize(column, resolved + STEP);
|
|
812
|
+
},
|
|
813
|
+
});
|
|
814
|
+
|
|
815
|
+
/** Returns whether the key was the table's, so a control that forwards its
|
|
816
|
+
* keys here keeps the ones it did not use. */
|
|
817
|
+
const handleKey = useCallback(
|
|
818
|
+
(ev: KeyboardEvent): boolean => {
|
|
819
|
+
const rows = orderedRef.current;
|
|
820
|
+
if (!rows.length || selectionMode === 'none') return false;
|
|
821
|
+
const at = rows.findIndex((r) => r.id === cursorRef.current);
|
|
822
|
+
const clamp = (i: number): number =>
|
|
823
|
+
Math.max(0, Math.min(rows.length - 1, i));
|
|
824
|
+
const move = (to: number): void => {
|
|
825
|
+
const row = rows[clamp(to)];
|
|
826
|
+
if (row) {
|
|
827
|
+
tap(row, {
|
|
828
|
+
ctrl: false,
|
|
829
|
+
shift: selectionMode === 'multiple' && ev.shiftKey,
|
|
830
|
+
});
|
|
831
|
+
}
|
|
832
|
+
};
|
|
833
|
+
/**
|
|
834
|
+
* How far a page key moves, in rows — with measured heights that is
|
|
835
|
+
* not a division: it is "where does this row's offset land after a
|
|
836
|
+
* viewport", with one row of overlap kept, and never standing still
|
|
837
|
+
* even when a row is taller than the viewport.
|
|
838
|
+
*/
|
|
839
|
+
const pageFrom = (from: number, dir: number): number => {
|
|
840
|
+
const viewport = viewRef.current.height;
|
|
841
|
+
if (viewport <= 0) return from + dir;
|
|
842
|
+
const here = clamp(from);
|
|
843
|
+
const target =
|
|
844
|
+
heights.offsetAt(here) + dir * Math.max(1, viewport - estimate);
|
|
845
|
+
const landed = heights.indexAt(Math.max(0, target));
|
|
846
|
+
return landed === here ? here + dir : landed;
|
|
847
|
+
};
|
|
848
|
+
|
|
849
|
+
switch (ev.keysym) {
|
|
850
|
+
case XK_UP:
|
|
851
|
+
move((at < 0 ? rows.length : at) - 1);
|
|
852
|
+
return true;
|
|
853
|
+
case XK_DOWN:
|
|
854
|
+
move((at < 0 ? -1 : at) + 1);
|
|
855
|
+
return true;
|
|
856
|
+
case XK_PAGE_UP:
|
|
857
|
+
move(pageFrom(at < 0 ? 0 : at, -1));
|
|
858
|
+
return true;
|
|
859
|
+
case XK_PAGE_DOWN:
|
|
860
|
+
move(pageFrom(at < 0 ? 0 : at, 1));
|
|
861
|
+
return true;
|
|
862
|
+
case XK_HOME:
|
|
863
|
+
move(0);
|
|
864
|
+
return true;
|
|
865
|
+
case XK_END:
|
|
866
|
+
move(rows.length - 1);
|
|
867
|
+
return true;
|
|
868
|
+
case XK_RETURN: {
|
|
869
|
+
const row = rows[at];
|
|
870
|
+
if (row) activate(row);
|
|
871
|
+
return true;
|
|
872
|
+
}
|
|
873
|
+
default:
|
|
874
|
+
break;
|
|
875
|
+
}
|
|
876
|
+
if (ev.codepoint === 32) {
|
|
877
|
+
const row = rows[at];
|
|
878
|
+
if (!row) return false;
|
|
879
|
+
if (selectionMode === 'multiple')
|
|
880
|
+
tap(row, { ctrl: true, shift: false });
|
|
881
|
+
else activate(row);
|
|
882
|
+
return true;
|
|
883
|
+
}
|
|
884
|
+
if (
|
|
885
|
+
selectionMode === 'multiple' &&
|
|
886
|
+
ev.ctrlKey &&
|
|
887
|
+
(ev.key === 'a' || ev.key === 'A')
|
|
888
|
+
) {
|
|
889
|
+
commitMulti(
|
|
890
|
+
rows.map((r) => r.id),
|
|
891
|
+
{ type: 'all' },
|
|
892
|
+
);
|
|
893
|
+
return true;
|
|
894
|
+
}
|
|
895
|
+
return false;
|
|
896
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps -- `heights` is
|
|
897
|
+
// a stable instance
|
|
898
|
+
},
|
|
899
|
+
[selectionMode, tap, activate, commitMulti, estimate],
|
|
900
|
+
);
|
|
901
|
+
|
|
902
|
+
useImperativeHandle(
|
|
903
|
+
ref,
|
|
904
|
+
(): TableHandle<Row> => ({
|
|
905
|
+
focus: () => {
|
|
906
|
+
root.current?.focus();
|
|
907
|
+
},
|
|
908
|
+
select: (id) => {
|
|
909
|
+
if (id === null) {
|
|
910
|
+
cursorRef.current = null;
|
|
911
|
+
setCursorId(null);
|
|
912
|
+
anchorRef.current = null;
|
|
913
|
+
if (selected !== undefined) return; // controlled: the caller owns it
|
|
914
|
+
if (selectionMode === 'multiple') setOwnMulti([]);
|
|
915
|
+
else setOwnSingle(null);
|
|
916
|
+
return;
|
|
917
|
+
}
|
|
918
|
+
const row = orderedRef.current.find((r) => r.id === id);
|
|
919
|
+
if (row) tap(row, { ctrl: false, shift: false });
|
|
920
|
+
},
|
|
921
|
+
scrollToRow: (id) => {
|
|
922
|
+
const row = orderedRef.current.find((r) => r.id === id);
|
|
923
|
+
if (!row) return false;
|
|
924
|
+
revealAt(row.index);
|
|
925
|
+
return true;
|
|
926
|
+
},
|
|
927
|
+
handleKey,
|
|
928
|
+
rows: () => orderedRef.current,
|
|
929
|
+
}),
|
|
930
|
+
[tap, handleKey, revealAt, selectionMode, selected],
|
|
931
|
+
);
|
|
932
|
+
|
|
933
|
+
// --- rendering -----------------------------------------------------------
|
|
934
|
+
|
|
935
|
+
const rowStyleProp = styles?.row;
|
|
936
|
+
const cellStyleProp = styles?.cell;
|
|
937
|
+
const headerCellStyleProp = styles?.headerCell;
|
|
938
|
+
const selectable = selectionMode !== 'none';
|
|
939
|
+
|
|
940
|
+
const renderOneRow = (entry: TableRow<Row>): ReactElement => {
|
|
941
|
+
const isSelected = selectedIds.has(entry.id);
|
|
942
|
+
const color = isSelected ? theme.hoverText : theme.text;
|
|
943
|
+
const state: TableRowState<Row> = { ...entry, selected: isSelected, color };
|
|
944
|
+
|
|
945
|
+
const content: ReactNode[] = columns.map((column, at) => {
|
|
946
|
+
const cellState: TableCellState<Row> = { ...state, column };
|
|
947
|
+
return hx(
|
|
948
|
+
'box',
|
|
949
|
+
{
|
|
950
|
+
key: column.id,
|
|
951
|
+
role: 'cell',
|
|
952
|
+
style: [
|
|
953
|
+
s.cell,
|
|
954
|
+
{ width: widths[at] },
|
|
955
|
+
uniform && { height: rowHeight },
|
|
956
|
+
column.align === 'end' && {
|
|
957
|
+
alignItems: 'flex-end',
|
|
958
|
+
paddingEnd: 8,
|
|
959
|
+
},
|
|
960
|
+
column.align === 'center' && { alignItems: 'center' },
|
|
961
|
+
typeof cellStyleProp === 'function'
|
|
962
|
+
? cellStyleProp(cellState)
|
|
963
|
+
: cellStyleProp,
|
|
964
|
+
],
|
|
965
|
+
},
|
|
966
|
+
column.render
|
|
967
|
+
? // A cell that draws itself still has to know it is on the
|
|
968
|
+
// selected row — see the doc comment. Keyed by the component,
|
|
969
|
+
// the way every seam's return is.
|
|
970
|
+
React.createElement(
|
|
971
|
+
React.Fragment,
|
|
972
|
+
{ key: 'content' },
|
|
973
|
+
column.render(entry.row, cellState),
|
|
974
|
+
)
|
|
975
|
+
: hx(
|
|
976
|
+
'text',
|
|
977
|
+
{ style: [s.cellText, { color }] },
|
|
978
|
+
String(columnValue(entry.row, column) ?? ''),
|
|
979
|
+
),
|
|
980
|
+
);
|
|
981
|
+
});
|
|
982
|
+
|
|
983
|
+
return hx(
|
|
984
|
+
'box',
|
|
985
|
+
{
|
|
986
|
+
key: String(entry.id),
|
|
987
|
+
role: 'row',
|
|
988
|
+
'aria-selected': selectable ? isSelected : undefined,
|
|
989
|
+
'aria-posinset': entry.index + 1,
|
|
990
|
+
'aria-setsize': ordered.length,
|
|
991
|
+
// The index the row was drawn at travels with the node, so measuring
|
|
992
|
+
// does not have to search the row list for it. It can go stale — the
|
|
993
|
+
// rows may move before the tick that measures — and both this and
|
|
994
|
+
// the height index check it rather than trust it.
|
|
995
|
+
ref: (node: DrawnNode | null) => {
|
|
996
|
+
if (node) rowNodes.current.set(entry.id, { node, at: entry.index });
|
|
997
|
+
else rowNodes.current.delete(entry.id);
|
|
998
|
+
},
|
|
999
|
+
onClick: (ev: MouseEvent) => {
|
|
1000
|
+
// A right-click also arrives here as a click; the selection it
|
|
1001
|
+
// implies is `onContextMenu`'s to make (select-unless-selected),
|
|
1002
|
+
// not the left button's replace.
|
|
1003
|
+
if (ev.button !== 1) return;
|
|
1004
|
+
tap(entry, { ctrl: ev.ctrlKey, shift: ev.shiftKey });
|
|
1005
|
+
// Select on the first click, open on the second — the gesture
|
|
1006
|
+
// every file list has. `detail` is the click count the renderer
|
|
1007
|
+
// already counts for text selection.
|
|
1008
|
+
if (ev.detail === 2) activate(entry);
|
|
1009
|
+
},
|
|
1010
|
+
onContextMenu: onRowContextMenu
|
|
1011
|
+
? (ev: MouseEvent) => {
|
|
1012
|
+
// The menu applies to what is under the pointer, so the row is
|
|
1013
|
+
// selected first — unless it is already part of the selection,
|
|
1014
|
+
// which a menu over "the selected files" must not collapse.
|
|
1015
|
+
if (selectable && !selectedRef.current.has(entry.id)) {
|
|
1016
|
+
tap(entry, { ctrl: false, shift: false });
|
|
1017
|
+
}
|
|
1018
|
+
onRowContextMenu(entry.id, entry.row, ev);
|
|
1019
|
+
}
|
|
1020
|
+
: undefined,
|
|
1021
|
+
style: [
|
|
1022
|
+
s.row,
|
|
1023
|
+
// Declared uniform: exactly this tall, content clipped — core's
|
|
1024
|
+
// row. Measured: a floor, and the row grows to whatever its
|
|
1025
|
+
// content needs; the height index reads back what it became.
|
|
1026
|
+
uniform
|
|
1027
|
+
? { height: rowHeight, alignItems: 'center' }
|
|
1028
|
+
: { minHeight: estimate },
|
|
1029
|
+
selectable && { cursor: 'pointer' },
|
|
1030
|
+
{
|
|
1031
|
+
backgroundColor: isSelected ? theme.hoverBackground : 'transparent',
|
|
1032
|
+
// The row's ink, said once: `color` inherits, so default cells
|
|
1033
|
+
// take it without being handed it.
|
|
1034
|
+
color,
|
|
1035
|
+
},
|
|
1036
|
+
selectable && {
|
|
1037
|
+
// pressed even on the selected row: a re-press on the row that
|
|
1038
|
+
// is already current is the one click in the table that would
|
|
1039
|
+
// otherwise look ignored
|
|
1040
|
+
':active': {
|
|
1041
|
+
backgroundColor: isSelected
|
|
1042
|
+
? theme.accentActive
|
|
1043
|
+
: theme.surfaceActive,
|
|
1044
|
+
},
|
|
1045
|
+
},
|
|
1046
|
+
selectable &&
|
|
1047
|
+
!isSelected && {
|
|
1048
|
+
':hover': { backgroundColor: theme.surfaceHover },
|
|
1049
|
+
},
|
|
1050
|
+
typeof rowStyleProp === 'function'
|
|
1051
|
+
? rowStyleProp(state)
|
|
1052
|
+
: rowStyleProp,
|
|
1053
|
+
],
|
|
1054
|
+
},
|
|
1055
|
+
renderRow ? renderRow(state, content) : content,
|
|
1056
|
+
);
|
|
1057
|
+
};
|
|
1058
|
+
|
|
1059
|
+
const headerCells = columns.map((column, at) => {
|
|
1060
|
+
const canSort = column.sortable !== false;
|
|
1061
|
+
const headerState: TableHeaderCellState<Row> = {
|
|
1062
|
+
column,
|
|
1063
|
+
sort: activeSort,
|
|
1064
|
+
resolvedWidth: widths[at],
|
|
1065
|
+
};
|
|
1066
|
+
const isLast = at === columns.length - 1;
|
|
1067
|
+
return React.createElement(
|
|
1068
|
+
React.Fragment,
|
|
1069
|
+
{ key: column.id },
|
|
1070
|
+
hx(
|
|
1071
|
+
'box',
|
|
1072
|
+
{
|
|
1073
|
+
role: 'columnheader',
|
|
1074
|
+
focusable: true,
|
|
1075
|
+
onClick: canSort ? () => toggleSort(column) : undefined,
|
|
1076
|
+
style: [
|
|
1077
|
+
s.header,
|
|
1078
|
+
// The header cell and the grab band are **siblings**, not a cell
|
|
1079
|
+
// with a handle inside it — a click fires on the nearest common
|
|
1080
|
+
// ancestor of press and release, so a grip nested in the header
|
|
1081
|
+
// made every resize end in a sort. The band is centred on the
|
|
1082
|
+
// rule and takes HALF from the column on each side, so every
|
|
1083
|
+
// header but the first is inset by that much.
|
|
1084
|
+
{
|
|
1085
|
+
width: Math.max(0, widths[at] - HALF - RULE - (at ? HALF : 0)),
|
|
1086
|
+
},
|
|
1087
|
+
canSort && { cursor: 'pointer' },
|
|
1088
|
+
typeof headerCellStyleProp === 'function'
|
|
1089
|
+
? headerCellStyleProp(headerState)
|
|
1090
|
+
: headerCellStyleProp,
|
|
1091
|
+
],
|
|
1092
|
+
},
|
|
1093
|
+
column.renderHeader
|
|
1094
|
+
? React.createElement(
|
|
1095
|
+
React.Fragment,
|
|
1096
|
+
{ key: 'content' },
|
|
1097
|
+
column.renderHeader(headerState),
|
|
1098
|
+
)
|
|
1099
|
+
: [
|
|
1100
|
+
hx(
|
|
1101
|
+
'text',
|
|
1102
|
+
{
|
|
1103
|
+
key: 'label',
|
|
1104
|
+
style: [s.headerLabel, { color: theme.text }],
|
|
1105
|
+
},
|
|
1106
|
+
column.label ?? column.id,
|
|
1107
|
+
),
|
|
1108
|
+
hx('box', { key: 'spring', style: s.headerSpring }),
|
|
1109
|
+
activeSort?.column === column.id
|
|
1110
|
+
? React.createElement(Icon, {
|
|
1111
|
+
key: 'mark',
|
|
1112
|
+
name:
|
|
1113
|
+
activeSort.direction === 'asc'
|
|
1114
|
+
? 'chevronUp'
|
|
1115
|
+
: 'chevronDown',
|
|
1116
|
+
size: SORT_MARK,
|
|
1117
|
+
color: theme.textMuted,
|
|
1118
|
+
style: s.sortMark,
|
|
1119
|
+
})
|
|
1120
|
+
: null,
|
|
1121
|
+
],
|
|
1122
|
+
),
|
|
1123
|
+
// The band is invisible until the pointer is on it, and answers the
|
|
1124
|
+
// press itself — a captured press keeps `:active` for the whole drag,
|
|
1125
|
+
// wherever the pointer wanders. The rule is its child, so the lit band
|
|
1126
|
+
// is symmetric about the line it moves.
|
|
1127
|
+
hx(
|
|
1128
|
+
'box',
|
|
1129
|
+
{
|
|
1130
|
+
...resizeProps(column, widths[at]),
|
|
1131
|
+
role: 'separator',
|
|
1132
|
+
'aria-orientation': 'vertical',
|
|
1133
|
+
'aria-label': `Resize ${column.label || column.id}`,
|
|
1134
|
+
'aria-valuenow': widths[at],
|
|
1135
|
+
'aria-valuemin': column.minWidth ?? MIN_COLUMN,
|
|
1136
|
+
style: [
|
|
1137
|
+
s.grip,
|
|
1138
|
+
{
|
|
1139
|
+
width: isLast ? HALF + RULE : GRIP,
|
|
1140
|
+
justifyContent: isLast ? 'flex-end' : 'center',
|
|
1141
|
+
},
|
|
1142
|
+
{ backgroundColor: 'transparent' },
|
|
1143
|
+
{ ':hover': { backgroundColor: theme.track } },
|
|
1144
|
+
{ ':active': { backgroundColor: theme.accent } },
|
|
1145
|
+
],
|
|
1146
|
+
},
|
|
1147
|
+
hx('box', { style: [s.rule, { backgroundColor: theme.border }] }),
|
|
1148
|
+
),
|
|
1149
|
+
);
|
|
1150
|
+
});
|
|
1151
|
+
|
|
1152
|
+
const bodyChildren: ReactNode[] = [];
|
|
1153
|
+
if (ordered.length === 0) {
|
|
1154
|
+
if (renderEmpty) {
|
|
1155
|
+
bodyChildren.push(
|
|
1156
|
+
hx('box', { key: 'empty', style: s.empty }, renderEmpty()),
|
|
1157
|
+
);
|
|
1158
|
+
}
|
|
1159
|
+
} else {
|
|
1160
|
+
if (virtualizing && first > 0) {
|
|
1161
|
+
bodyChildren.push(
|
|
1162
|
+
hx('box', {
|
|
1163
|
+
key: 'spacer:before',
|
|
1164
|
+
style: [s.spacer, { height: above }],
|
|
1165
|
+
}),
|
|
1166
|
+
);
|
|
1167
|
+
}
|
|
1168
|
+
for (let i = first; i < last; i++)
|
|
1169
|
+
bodyChildren.push(renderOneRow(ordered[i]));
|
|
1170
|
+
if (virtualizing && last < ordered.length) {
|
|
1171
|
+
bodyChildren.push(
|
|
1172
|
+
hx('box', {
|
|
1173
|
+
key: 'spacer:after',
|
|
1174
|
+
style: [s.spacer, { height: below }],
|
|
1175
|
+
}),
|
|
1176
|
+
);
|
|
1177
|
+
}
|
|
1178
|
+
}
|
|
1179
|
+
|
|
1180
|
+
return hx(
|
|
1181
|
+
'box',
|
|
1182
|
+
{
|
|
1183
|
+
theme,
|
|
1184
|
+
// The table role, with the honest caveat that virtualization keeps
|
|
1185
|
+
// only the rendered rows in the accessible tree. `aria-label` via
|
|
1186
|
+
// boxProps names the table.
|
|
1187
|
+
role: 'table',
|
|
1188
|
+
// the *table* takes focus, not the row — see the doc comment
|
|
1189
|
+
focusable,
|
|
1190
|
+
...boxProps,
|
|
1191
|
+
ref: root,
|
|
1192
|
+
style: [s.root, style],
|
|
1193
|
+
/**
|
|
1194
|
+
* `preventDefault` is the load-bearing half: the focused table sits
|
|
1195
|
+
* over a scroll container with default key actions of its own, and
|
|
1196
|
+
* without it every arrow both moved the selection and scrolled the
|
|
1197
|
+
* list under it. A key the table did not take keeps its default.
|
|
1198
|
+
*/
|
|
1199
|
+
onKeyDown: (ev) => {
|
|
1200
|
+
if (handleKey(ev)) ev.preventDefault();
|
|
1201
|
+
},
|
|
1202
|
+
},
|
|
1203
|
+
// the header scrolls sideways with the body but never vertically, so it
|
|
1204
|
+
// lives outside the scrolling pane and is shifted by the body's scrollX —
|
|
1205
|
+
// `marginStart`, so it tracks in the direction the columns actually run
|
|
1206
|
+
hx(
|
|
1207
|
+
'box',
|
|
1208
|
+
{
|
|
1209
|
+
style: [
|
|
1210
|
+
s.headerClip,
|
|
1211
|
+
{ backgroundColor: theme.surfaceHover },
|
|
1212
|
+
styles?.header,
|
|
1213
|
+
],
|
|
1214
|
+
},
|
|
1215
|
+
hx(
|
|
1216
|
+
'box',
|
|
1217
|
+
{ style: [s.headerRow, { marginStart: -scrollX, width: total }] },
|
|
1218
|
+
headerCells,
|
|
1219
|
+
),
|
|
1220
|
+
),
|
|
1221
|
+
hx(
|
|
1222
|
+
'box',
|
|
1223
|
+
{
|
|
1224
|
+
ref: body,
|
|
1225
|
+
style: s.body,
|
|
1226
|
+
onScroll: (ev) => {
|
|
1227
|
+
// A scroll this component did not ask for is the user taking over,
|
|
1228
|
+
// and an owed `scrollToRow` must not yank the list back out from
|
|
1229
|
+
// under them on the next layout.
|
|
1230
|
+
reveal.heard(ev.scrollY);
|
|
1231
|
+
setScrollX((prev) => (prev === ev.scrollX ? prev : ev.scrollX));
|
|
1232
|
+
if (virtualizing) {
|
|
1233
|
+
setView((prev) =>
|
|
1234
|
+
prev.top === ev.scrollY ? prev : { ...prev, top: ev.scrollY },
|
|
1235
|
+
);
|
|
1236
|
+
}
|
|
1237
|
+
onScroll?.(ev);
|
|
1238
|
+
},
|
|
1239
|
+
// Layout, not scrolling, is what first tells a table how much of it
|
|
1240
|
+
// is worth building — and the viewport width is what the flex
|
|
1241
|
+
// columns resolve against, so this is measured whether or not the
|
|
1242
|
+
// table virtualizes.
|
|
1243
|
+
onViewport: (ev) => {
|
|
1244
|
+
// The ref first, at event time: the measure tick runs before the
|
|
1245
|
+
// re-render this setState causes, and it must see this viewport.
|
|
1246
|
+
viewRef.current = {
|
|
1247
|
+
...viewRef.current,
|
|
1248
|
+
height: ev.height,
|
|
1249
|
+
width: ev.width,
|
|
1250
|
+
};
|
|
1251
|
+
setView((prev) =>
|
|
1252
|
+
prev.height === ev.height && prev.width === ev.width
|
|
1253
|
+
? prev
|
|
1254
|
+
: { ...prev, height: ev.height, width: ev.width },
|
|
1255
|
+
);
|
|
1256
|
+
// The content just changed size, which is both the moment an owed
|
|
1257
|
+
// scroll can reach further than the clamp let it and the moment the
|
|
1258
|
+
// container may have re-clamped the offset without saying so. It is
|
|
1259
|
+
// not a moment anything can be *settled* in while rows are still
|
|
1260
|
+
// being measured: this runs from layout, a tick before the pass that
|
|
1261
|
+
// reads those rows back.
|
|
1262
|
+
reveal.retry(virtualizing && !uniform);
|
|
1263
|
+
syncScroll();
|
|
1264
|
+
onViewport?.(ev);
|
|
1265
|
+
},
|
|
1266
|
+
},
|
|
1267
|
+
hx('box', { style: [s.rowsBox, { width: total }] }, bodyChildren),
|
|
1268
|
+
),
|
|
1269
|
+
);
|
|
1270
|
+
}
|