buninu-browser 0.3.0 → 0.4.0
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/LICENSES/jsmdcui.txt +22 -0
- package/README.md +19 -4
- package/frontends/wayland/LICENSE-land +21 -0
- package/frontends/wayland/README.md +123 -0
- package/frontends/wayland/casty/LICENSE +21 -0
- package/frontends/wayland/casty/bookmarks.js +32 -0
- package/frontends/wayland/casty/click.js +96 -0
- package/frontends/wayland/casty/commands.js +61 -0
- package/frontends/wayland/casty/config.js +45 -0
- package/frontends/wayland/casty/download-directory.js +28 -0
- package/frontends/wayland/casty/download.js +49 -0
- package/frontends/wayland/casty/hints.js +334 -0
- package/frontends/wayland/casty/keys.js +56 -0
- package/frontends/wayland/casty/page-commands.js +80 -0
- package/frontends/wayland/casty/prompt-width.js +148 -0
- package/frontends/wayland/casty/urlbar.js +606 -0
- package/frontends/wayland/client.js +88 -0
- package/frontends/wayland/clipboard.js +80 -0
- package/frontends/wayland/index.js +116 -0
- package/frontends/wayland/input.js +56 -0
- package/frontends/wayland/protocol.js +55 -0
- package/frontends/wayland/protocols/wayland.xml +3248 -0
- package/frontends/wayland/protocols/xdg-shell.xml +1415 -0
- package/frontends/wayland/transport.js +120 -0
- package/frontends/wayland/urlbar.js +158 -0
- package/index.js +28 -1
- package/lib/clipboard-command.js +10 -0
- package/lib/clipboard-powershell.js +22 -0
- package/lib/clipboard.js +37 -2
- package/lib/document-types.js +11 -0
- package/lib/headless-shell.js +47 -8
- package/lib/layout/text-layout.js +17 -9
- package/lib/paint/display-list.js +5 -1
- package/lib/qml/README.md +971 -0
- package/lib/qml/builtins.js +700 -0
- package/lib/qml/canvas-dom.js +115 -0
- package/lib/qml/document.js +1176 -0
- package/lib/qml/format.js +9 -0
- package/lib/qml/host.js +324 -0
- package/lib/qml/images.js +199 -0
- package/lib/qml/launcher.js +132 -0
- package/lib/qml/loader.js +517 -0
- package/lib/qml/media-renderer.js +49 -0
- package/lib/qml/media.js +86 -0
- package/lib/qml/models.js +622 -0
- package/lib/qml/parser.js +381 -0
- package/lib/qml/qs +5 -0
- package/lib/qml/qscasty +3 -0
- package/lib/qml/qsheadless +3 -0
- package/lib/qml/qswayland +3 -0
- package/lib/qml/reactive.js +159 -0
- package/lib/qml/runtime.js +3381 -0
- package/lib/qml/shapes.js +243 -0
- package/lib/qml/text.js +109 -0
- package/lib/qml/textures.js +123 -0
- package/lib/qml/wayland-clipboard.js +32 -0
- package/lib/qml/worker-thread.js +70 -0
- package/lib/render-tree/index.js +16 -1
- package/lib/renderer/bindings.js +11 -0
- package/lib/renderer/canvas-2d.js +13 -0
- package/lib/renderer/host.js +31 -6
- package/lib/renderer/main.js +19 -6
- package/lib/renderer/page-realm.js +16 -1
- package/lib/renderer/page-renderer.js +230 -13
- package/lib/renderer/text-input-paint.js +54 -0
- package/lib/renderer/text-input.js +57 -0
- package/lib/style/computed-style.js +2 -2
- package/package.json +1 -1
- package/test/clipboard.test.js +19 -0
- package/test/fixtures/flea-helpers/Dockerfile +36 -0
- package/test/fixtures/flea-helpers/bat.mjs +43 -0
- package/test/fixtures/flea-helpers/bin/bat +3 -0
- package/test/fixtures/flea-helpers/bin/bwrap +10 -0
- package/test/fixtures/flea-helpers/bin/bwrap-real-filter +9 -0
- package/test/fixtures/flea-helpers/bin/glow +3 -0
- package/test/fixtures/flea-helpers/bin/qs +3 -0
- package/test/fixtures/flea-helpers/bin/unsupported +3 -0
- package/test/fixtures/flea-helpers/check-highlighting.mjs +47 -0
- package/test/fixtures/flea-helpers/check-media.mjs +29 -0
- package/test/fixtures/flea-helpers/check-preview.mjs +27 -0
- package/test/fixtures/flea-helpers/check.mjs +36 -0
- package/test/fixtures/flea-helpers/highlight-cases.json +227 -0
- package/test/fixtures/flea-helpers/source-mime.xml +28 -0
- package/test/fixtures/flea-helpers/syntax-compat.mjs +23 -0
- package/test/fixtures/flea-thumbnail-stub/flea-placeholder.thumbnailer +4 -0
- package/test/fixtures/flea-thumbnail-stub/flea-thumbnail-stub +3 -0
- package/test/fixtures/qml/flea/EscapeHint.qml +11 -0
- package/test/fixtures/qml/flea/LICENSE +21 -0
- package/test/fixtures/qml/flea/README.md +25 -0
- package/test/fixtures/qml/flea/Theme.qml +8 -0
- package/test/fixtures/qml/flea/demo.qml +24 -0
- package/test/fixtures/qml/flea/qmldir +3 -0
- package/test/fixtures/qml/flea-theme/LICENSE-Flea +21 -0
- package/test/fixtures/qml/flea-theme/LICENSE-Omarchy +20 -0
- package/test/fixtures/qml/flea-theme/README.md +70 -0
- package/test/fixtures/qml/flea-theme/button.qml +17 -0
- package/test/fixtures/qml/flea-theme/demo.qml +27 -0
- package/test/fixtures/qml/flea-theme/field.qml +21 -0
- package/test/fixtures/qml/flea-theme/imports/qs/Commons/BorderGeometry.js +373 -0
- package/test/fixtures/qml/flea-theme/imports/qs/Commons/Color.qml +258 -0
- package/test/fixtures/qml/flea-theme/imports/qs/Commons/I18n.qml +118 -0
- package/test/fixtures/qml/flea-theme/imports/qs/Commons/Numbers.js +19 -0
- package/test/fixtures/qml/flea-theme/imports/qs/Commons/Plural.js +91 -0
- package/test/fixtures/qml/flea-theme/imports/qs/Commons/Style.qml +561 -0
- package/test/fixtures/qml/flea-theme/imports/qs/Commons/Util.qml +188 -0
- package/test/fixtures/qml/flea-theme/imports/qs/Commons/qmldir +13 -0
- package/test/fixtures/qml/flea-theme/segment.qml +17 -0
- package/test/fixtures/qml/flea-theme/settings.qml +58 -0
- package/test/fixtures/qml/flea-theme/text-metrics.qml +10 -0
- package/test/fixtures/qml/flea-theme/ui/DialogButton.qml +150 -0
- package/test/fixtures/qml/flea-theme/ui/DialogField.qml +147 -0
- package/test/fixtures/qml/flea-theme/ui/EscapeHint.qml +11 -0
- package/test/fixtures/qml/flea-theme/ui/Glass.qml +114 -0
- package/test/fixtures/qml/flea-theme/ui/Glyph.qml +54 -0
- package/test/fixtures/qml/flea-theme/ui/GlyphSolid.qml +22 -0
- package/test/fixtures/qml/flea-theme/ui/Reveal.qml +54 -0
- package/test/fixtures/qml/flea-theme/ui/SettingsRuler.qml +85 -0
- package/test/fixtures/qml/flea-theme/ui/SettingsSegment.qml +85 -0
- package/test/fixtures/qml/flea-theme/ui/StableModel.qml +89 -0
- package/test/fixtures/qml/flea-theme/ui/Theme.qml +460 -0
- package/test/fixtures/qml/flea-theme/ui/ThemeColors.qml +43 -0
- package/test/fixtures/qml/flea-theme/ui/ViewState.qml +565 -0
- package/test/fixtures/qml/flea-theme/ui/js/Anchor.js +379 -0
- package/test/fixtures/qml/flea-theme/ui/js/AnchorHold.js +162 -0
- package/test/fixtures/qml/flea-theme/ui/js/Archive.js +157 -0
- package/test/fixtures/qml/flea-theme/ui/js/BackReturn.js +110 -0
- package/test/fixtures/qml/flea-theme/ui/js/Buttons.js +54 -0
- package/test/fixtures/qml/flea-theme/ui/js/ColumnMenu.js +111 -0
- package/test/fixtures/qml/flea-theme/ui/js/Columns.js +298 -0
- package/test/fixtures/qml/flea-theme/ui/js/Contrast.js +157 -0
- package/test/fixtures/qml/flea-theme/ui/js/Density.js +44 -0
- package/test/fixtures/qml/flea-theme/ui/js/DirSizes.js +129 -0
- package/test/fixtures/qml/flea-theme/ui/js/ExtThumbs.js +115 -0
- package/test/fixtures/qml/flea-theme/ui/js/Filter.js +284 -0
- package/test/fixtures/qml/flea-theme/ui/js/FolderSorts.js +108 -0
- package/test/fixtures/qml/flea-theme/ui/js/Format.js +305 -0
- package/test/fixtures/qml/flea-theme/ui/js/Icons.js +206 -0
- package/test/fixtures/qml/flea-theme/ui/js/Input.js +39 -0
- package/test/fixtures/qml/flea-theme/ui/js/KeyName.js +64 -0
- package/test/fixtures/qml/flea-theme/ui/js/Keymap.js +370 -0
- package/test/fixtures/qml/flea-theme/ui/js/Kinds.js +145 -0
- package/test/fixtures/qml/flea-theme/ui/js/Lists.js +11 -0
- package/test/fixtures/qml/flea-theme/ui/js/MakeDefault.js +186 -0
- package/test/fixtures/qml/flea-theme/ui/js/Match.js +27 -0
- package/test/fixtures/qml/flea-theme/ui/js/Material.js +109 -0
- package/test/fixtures/qml/flea-theme/ui/js/Menu.js +513 -0
- package/test/fixtures/qml/flea-theme/ui/js/Motion.js +16 -0
- package/test/fixtures/qml/flea-theme/ui/js/Mounts.js +320 -0
- package/test/fixtures/qml/flea-theme/ui/js/MouseNav.js +13 -0
- package/test/fixtures/qml/flea-theme/ui/js/Nav.js +328 -0
- package/test/fixtures/qml/flea-theme/ui/js/Palette.js +33 -0
- package/test/fixtures/qml/flea-theme/ui/js/Places.js +236 -0
- package/test/fixtures/qml/flea-theme/ui/js/Protocols.js +296 -0
- package/test/fixtures/qml/flea-theme/ui/js/RecentMode.js +154 -0
- package/test/fixtures/qml/flea-theme/ui/js/Search.js +396 -0
- package/test/fixtures/qml/flea-theme/ui/js/Settings.js +476 -0
- package/test/fixtures/qml/flea-theme/ui/js/SettingsAbout.js +99 -0
- package/test/fixtures/qml/flea-theme/ui/js/SettingsShelf.js +52 -0
- package/test/fixtures/qml/flea-theme/ui/js/Sort.js +127 -0
- package/test/fixtures/qml/flea-theme/ui/js/StateWords.js +42 -0
- package/test/fixtures/qml/flea-theme/ui/js/Swap.js +166 -0
- package/test/fixtures/qml/flea-theme/ui/js/TextSize.js +107 -0
- package/test/fixtures/qml/flea-theme/ui/js/Thumbs.js +158 -0
- package/test/fixtures/qml/flea-theme/ui/js/UiState.js +438 -0
- package/test/fixtures/qml/flea-theme/ui/js/Update.js +132 -0
- package/test/fixtures/qml/flea-theme/ui/js/UserDirs.js +56 -0
- package/test/fixtures/qml/flea-theme/ui/qmldir +227 -0
- package/test/fixtures/qml/hello.qml +26 -0
- package/test/fixtures/qml/imports/Demo/Toolkit/VersionLabel.qml +2 -0
- package/test/fixtures/qml/imports/Demo/Toolkit/VersionLabelNew.qml +2 -0
- package/test/fixtures/qml/imports/Demo/Toolkit/qmldir +3 -0
- package/test/fixtures/qml/imports/components/Badge.qml +15 -0
- package/test/fixtures/qml/imports/logic/base.js +2 -0
- package/test/fixtures/qml/imports/logic/counter.js +4 -0
- package/test/fixtures/qml/imports/logic/math.js +3 -0
- package/test/fixtures/qml/imports/logic/shared.js +3 -0
- package/test/fixtures/qml/imports/main.qml +21 -0
- package/test/fixtures/qml/imports/theme/Theme.qml +8 -0
- package/test/fixtures/qml/imports/theme/qmldir +2 -0
- package/test/fixtures/qml/multiply.qml +61 -0
- package/test/fixtures/qml-canvas-dom.js +90 -0
- package/test/fixtures/qml-media-audio.js +39 -0
- package/test/helpers/flea-sandbox.js +48 -0
- package/test/helpers/quickshell-fixture.js +125 -0
- package/test/html-clipboard-wayland.test.js +47 -0
- package/test/index.test.js +13 -0
- package/test/manual-casty-without-qml.js +87 -0
- package/test/manual-flea-application.js +49 -0
- package/test/manual-flea-binary.js +62 -0
- package/test/manual-flea-qml.js +9 -0
- package/test/manual-flea-settings.js +40 -0
- package/test/manual-flea-theme.js +40 -0
- package/test/manual-qml-removal.js +46 -0
- package/test/manual-without-qml.js +45 -0
- package/test/qml-canvas-dom.test.js +37 -0
- package/test/qml-clipboard-bridge.test.js +25 -0
- package/test/qml-clipboard-wayland.test.js +40 -0
- package/test/qml-connections-timing.test.js +73 -0
- package/test/qml-dynamic.test.js +401 -0
- package/test/qml-flea-app.test.js +146 -0
- package/test/qml-flea-backend.test.js +78 -0
- package/test/qml-flea-columns-backend.test.js +38 -0
- package/test/qml-flea-compile.test.js +78 -0
- package/test/qml-flea-launcher.test.js +88 -0
- package/test/qml-flea-markdown-backend.test.js +31 -0
- package/test/qml-flea-media.test.js +28 -0
- package/test/qml-flea-navigation-backend.test.js +35 -0
- package/test/qml-flea-navigation-timing.test.js +147 -0
- package/test/qml-flea-preview-backend.test.js +53 -0
- package/test/qml-flea-previews.test.js +163 -0
- package/test/qml-flea-rows.test.js +101 -0
- package/test/qml-flea-textures.test.js +59 -0
- package/test/qml-flea-view-switch.test.js +33 -0
- package/test/qml-flea-wayland.test.js +68 -0
- package/test/qml-flea.test.js +34 -0
- package/test/qml-host.test.js +238 -0
- package/test/qml-imports.test.js +102 -0
- package/test/qml-inherited-handlers.test.js +51 -0
- package/test/qml-input-selection.test.js +66 -0
- package/test/qml-interactions.test.js +309 -0
- package/test/qml-language.test.js +167 -0
- package/test/qml-launcher.test.js +54 -0
- package/test/qml-layout-text.test.js +205 -0
- package/test/qml-media.test.js +63 -0
- package/test/qml-persisted-delegates.test.js +45 -0
- package/test/qml-quickshell-controls.test.js +65 -0
- package/test/qml-quickshell-settings.test.js +62 -0
- package/test/qml-repeater.test.js +72 -0
- package/test/qml-style-updates.test.js +124 -0
- package/test/qml-textures.test.js +66 -0
- package/test/qml-views.test.js +332 -0
- package/test/qml-worker.test.js +69 -0
- package/test/qml.test.js +83 -0
- package/test/renderer-host.test.js +11 -0
- package/test/text-input-pointer.test.js +31 -0
- package/test/text-input-scroll.test.js +29 -0
- package/test/text-input.test.js +47 -0
- package/test/wayland-frontend.test.js +83 -0
- package/test/wayland-urlbar.test.js +38 -0
|
@@ -0,0 +1,379 @@
|
|
|
1
|
+
.pragma library
|
|
2
|
+
|
|
3
|
+
.import "AnchorHold.js" as Hold
|
|
4
|
+
|
|
5
|
+
// Anchor locate IDs sit above QML's signed-int menu IDs, keeping the reply askers disjoint.
|
|
6
|
+
var LOCATE_ID_FLOOR = 2147483648
|
|
7
|
+
var locateSeq = LOCATE_ID_FLOOR
|
|
8
|
+
|
|
9
|
+
// Re-reading the open listing without moving the user off it, for a foreign change and Flea's own delete.
|
|
10
|
+
|
|
11
|
+
// A re-read renumbers every row, so it waits while an interaction owns the rows or while any paths asker resolves.
|
|
12
|
+
function busy(pane, anchor) {
|
|
13
|
+
if (!pane)
|
|
14
|
+
return true
|
|
15
|
+
if (anchor && anchor.path === pane.path)
|
|
16
|
+
return true
|
|
17
|
+
if (pane.menuActions && (pane.menuActions.pendingAction || pane.menuActions.pendingActivation === true))
|
|
18
|
+
return true
|
|
19
|
+
if (pane.dragActive === true || pane.awaitingPaths === true || pane.pointerHeld === true)
|
|
20
|
+
return true
|
|
21
|
+
if (pane.pathsPending)
|
|
22
|
+
return true
|
|
23
|
+
if (pane.clipPending !== undefined && pane.clipPending !== null)
|
|
24
|
+
return true
|
|
25
|
+
return pane.listInFlight || pane.renamingIndex >= 0 || pane.renamePending
|
|
26
|
+
|| pane.menuVisible || (pane.menuActions && pane.menuActions.opened) || pane.filterTyping || pane.searchMode.length > 0
|
|
27
|
+
|| pane.selectionBand !== null || (pane.collide && pane.collide.pending !== null)
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function watched(pane, wantChanged, rowH) {
|
|
31
|
+
return anchoredRefresh(pane, false, selectedMarks(pane), wantChanged, rowH)
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Flea's own delete: the anchor is the deleted cursor row and apply()'s fallback lands on whatever took its place, which is Finder's rule. A failed delete matches by name instead.
|
|
35
|
+
// Sample input: afterDelete(pane, true) with pane.trashedFirst set by Ops.trash.
|
|
36
|
+
function afterDelete(pane, landed) {
|
|
37
|
+
if (landed && pane.trashedFirst >= 0)
|
|
38
|
+
pane.cursorIndex = pane.trashedFirst
|
|
39
|
+
pane.trashedFirst = -1
|
|
40
|
+
return anchoredRefresh(pane, true, null, false)
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// A listing past its wait stops loading the pane: late rows are dropped as replaced.
|
|
44
|
+
function clearWaiting(pane) {
|
|
45
|
+
if (pane.listingState === "waiting") {
|
|
46
|
+
pane.listInFlight = false
|
|
47
|
+
pane.listingState = "loading"
|
|
48
|
+
pane.stateMessage = ""
|
|
49
|
+
return true
|
|
50
|
+
}
|
|
51
|
+
return false
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Which row a full target path names, NFC-matched the way the backend lists it.
|
|
55
|
+
function selectMatch(rows, target, folder) {
|
|
56
|
+
var base = String(folder || "")
|
|
57
|
+
var text = String(target || "")
|
|
58
|
+
var leaf = base.length <= 1 ? text.substring(1) : text.substring(base.length + 1)
|
|
59
|
+
if (base.length > 1 && text.substring(0, base.length + 1) !== base + "/")
|
|
60
|
+
return -1
|
|
61
|
+
var want = typeof leaf.normalize === "function" ? leaf.normalize("NFC") : leaf
|
|
62
|
+
for (var i = 0; i < rows.length; i++) {
|
|
63
|
+
if (String(rows[i].n || "") === leaf)
|
|
64
|
+
return i
|
|
65
|
+
}
|
|
66
|
+
for (var j = 0; j < rows.length; j++) {
|
|
67
|
+
var name = String(rows[j].n || "")
|
|
68
|
+
var norm = typeof name.normalize === "function" ? name.normalize("NFC") : name
|
|
69
|
+
if (norm === want)
|
|
70
|
+
return j
|
|
71
|
+
}
|
|
72
|
+
return -1
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// The cursor index for a full target path, or -1 when the listing holds no such row.
|
|
76
|
+
function matchListed(pane, target) {
|
|
77
|
+
var at = selectMatch(pane.rows, target, pane.path)
|
|
78
|
+
return at >= 0 ? pane.held + at : -1
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// A rename commit keeps the renamed row (source leaf mapped to destination) unless moved: a click or key took the cursor to another row.
|
|
82
|
+
function pointerRow(pane, request, rowH) {
|
|
83
|
+
var row = pane.rowFor(pane.cursorIndex)
|
|
84
|
+
var name = row ? String(row.n) : ""
|
|
85
|
+
var src = Hold.holdLeaf(request.source)
|
|
86
|
+
var dst = request.destination ? Hold.holdLeaf(request.destination) : ""
|
|
87
|
+
var moved = name.length > 0 && name !== src && name !== dst
|
|
88
|
+
if (name === src && dst.length > 0)
|
|
89
|
+
name = dst
|
|
90
|
+
var place = Hold.viewFields(pane, rowH)
|
|
91
|
+
return { name: name, index: pane.cursorIndex, start: pane.held, path: pane.path, select: true, moved: moved,
|
|
92
|
+
offset: place.offset, rowH: place.rowH, view: place.view, renamed: !moved && dst.length > 0 && name === dst }
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function anchoredRefresh(pane, select, marks, wantChanged, rowH) {
|
|
96
|
+
if (pane.listInFlight) {
|
|
97
|
+
return null
|
|
98
|
+
}
|
|
99
|
+
var row = pane.rowFor(pane.cursorIndex)
|
|
100
|
+
var lone = pane.selection && typeof pane.selection.follows === "function" ? !!pane.selection.follows() : false
|
|
101
|
+
var place = Hold.viewFields(pane, rowH)
|
|
102
|
+
// The path rides along because the anchor can outlive one rows reply: a navigation between the two below would otherwise put this directory's cursor row onto the next directory's listing.
|
|
103
|
+
var anchor = { name: row ? String(row.n) : "", index: pane.cursorIndex, start: pane.held,
|
|
104
|
+
path: pane.path, select: select === true, wantChanged: wantChanged === true, reloadFrom: pane.total,
|
|
105
|
+
marks: marks || null, kept: [], hadMarks: marks !== null && marks.length > 0,
|
|
106
|
+
lone: lone, offset: place.offset, rowH: place.rowH, view: place.view }
|
|
107
|
+
// F2: names outside the held window come from one batched paths request before the swap, tagged so the reply reaches only this asker.
|
|
108
|
+
if (anchor.marks) {
|
|
109
|
+
var need = []
|
|
110
|
+
for (var i = 0; i < anchor.marks.length; i++) {
|
|
111
|
+
if (anchor.marks[i].name === null)
|
|
112
|
+
need.push(anchor.marks[i].index)
|
|
113
|
+
}
|
|
114
|
+
if (need.length > 0 && !pane.pathsPending && (pane.clipPending === undefined || pane.clipPending === null)
|
|
115
|
+
&& pane.backend && pane.backend.send) {
|
|
116
|
+
pane.backend.send({ c: "paths", rows: need })
|
|
117
|
+
anchor.needPaths = need
|
|
118
|
+
pane.pathsPending = { kind: "anchor" }
|
|
119
|
+
return anchor
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
Hold.startList(pane, anchor)
|
|
123
|
+
return anchor
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// The batched paths answer for F2: fills unheld names, then starts the list the swap holds.
|
|
127
|
+
function fillPaths(pane, anchor, list) {
|
|
128
|
+
return Hold.fillPaths(pane, anchor, list)
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// True only for the anchor's own tagged reply; a clipboard, drag or compress reply takes nothing.
|
|
132
|
+
function takesPaths(pane, anchor) {
|
|
133
|
+
return !!(anchor && anchor.needPaths && pane.pathsPending && pane.pathsPending.kind === "anchor")
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// A failed paths ask keeps the anchor so the shifted listing lands it by name; a refused locate ends it.
|
|
137
|
+
// Sample input: failAnchor(pane, { name: "b", index: 1, marks: [], kept: [] }) keeps the anchor for rows ["NEW","a","b","c"].
|
|
138
|
+
function failAnchor(pane, anchor, rowH) {
|
|
139
|
+
if (!anchor)
|
|
140
|
+
return null
|
|
141
|
+
var hadPaths = !!anchor.needPaths
|
|
142
|
+
anchor.needPaths = null
|
|
143
|
+
anchor.locateDone = true
|
|
144
|
+
if (pane.pathsPending && pane.pathsPending.kind === "anchor")
|
|
145
|
+
pane.pathsPending = null
|
|
146
|
+
if (hadPaths) {
|
|
147
|
+
if (pane.path !== anchor.path)
|
|
148
|
+
return null
|
|
149
|
+
// Land by name on the held rows before the re-list renumbers them; a miss waits for the shifted listing.
|
|
150
|
+
var at = indexOf(pane, anchor.name)
|
|
151
|
+
if (pane.total > 0 && at >= 0) {
|
|
152
|
+
landOn(pane, at, anchor)
|
|
153
|
+
Hold.restoreView(pane, anchor, rowH)
|
|
154
|
+
}
|
|
155
|
+
// A failed ask still owes the listing that draws the outside change, unless one is already out.
|
|
156
|
+
if (!pane.listInFlight)
|
|
157
|
+
Hold.startList(pane, anchor)
|
|
158
|
+
// Keep the target across the renumber so apply() re-lands it; ending here would strand the pre-list index.
|
|
159
|
+
return anchor
|
|
160
|
+
}
|
|
161
|
+
var found = indexOf(pane, anchor.name)
|
|
162
|
+
if (pane.total > 0) {
|
|
163
|
+
landOn(pane, found >= 0 ? found : Math.min(anchor.index, pane.total - 1), anchor)
|
|
164
|
+
Hold.restoreView(pane, anchor, rowH)
|
|
165
|
+
}
|
|
166
|
+
finishMarks(pane, anchor)
|
|
167
|
+
return null
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// PaneWire's located guard consumes only this anchor's request; a refused own reply ends the anchor.
|
|
171
|
+
// Sample input: { directory: "/d", id: 2147483649, transferId: 0, ok: true, matches: [{ path: "/d/a", index: 3 }] }.
|
|
172
|
+
function takeLocated(pane, anchor, message, rowH) {
|
|
173
|
+
if (!anchor || !anchor.locateSent || anchor.locateDone)
|
|
174
|
+
return { handled: false, anchor: anchor }
|
|
175
|
+
if (!message || message.directory !== pane.path || message.id !== anchor.locateId || message.transferId !== 0)
|
|
176
|
+
return { handled: false, anchor: anchor }
|
|
177
|
+
if (message.ok === false)
|
|
178
|
+
return { handled: true, anchor: failAnchor(pane, anchor, rowH) }
|
|
179
|
+
return { handled: true, anchor: fillLocated(pane, anchor, message.matches || [], rowH) }
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// The marks the re-read carries across by file identity: an index renumbers under a fresh list, so each marked row's name is recorded and looked up again when the rows land. A gone name loses its mark rather than re-pointing at another file.
|
|
183
|
+
function selectedMarks(pane) {
|
|
184
|
+
if (!pane.selectedIndices)
|
|
185
|
+
return []
|
|
186
|
+
var out = []
|
|
187
|
+
var indices = pane.selectedIndices()
|
|
188
|
+
for (var i = 0; i < indices.length; i++) {
|
|
189
|
+
var row = pane.rowFor(indices[i])
|
|
190
|
+
out.push({ index: indices[i], name: row ? String(row.n) : null })
|
|
191
|
+
}
|
|
192
|
+
return out
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// The listing row holding a name in the window the pane holds now, or -1.
|
|
196
|
+
function indexOf(pane, name) {
|
|
197
|
+
for (var i = 0; i < pane.rows.length; i++) {
|
|
198
|
+
if (String(pane.rows[i].n) === name)
|
|
199
|
+
return pane.held + i
|
|
200
|
+
}
|
|
201
|
+
return -1
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// Moves whatever the held window holds from pending to kept, naming nothing on screen: the take lands once, when the anchor resolves, so no rows reply in between draws a half-restored set.
|
|
205
|
+
function sweepMarks(pane, anchor) {
|
|
206
|
+
if (!anchor.marks)
|
|
207
|
+
return
|
|
208
|
+
var pending = []
|
|
209
|
+
for (var m = 0; m < anchor.marks.length; m++) {
|
|
210
|
+
var mark = anchor.marks[m]
|
|
211
|
+
if (mark.name === null)
|
|
212
|
+
continue
|
|
213
|
+
var at = indexOf(pane, mark.name)
|
|
214
|
+
if (at >= 0)
|
|
215
|
+
anchor.kept.push(at)
|
|
216
|
+
else
|
|
217
|
+
pending.push(mark)
|
|
218
|
+
}
|
|
219
|
+
anchor.marks = pending
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// The one take, on the anchor resolving: the rows the re-read cleared are marked again on the same files, and whatever is gone stays gone. A lone selection restores with only(), the way Tabs.restoreSelection does, so a plain click never turns sticky behind the re-read.
|
|
223
|
+
function finishMarks(pane, anchor) {
|
|
224
|
+
var kept = anchor.kept || []
|
|
225
|
+
anchor.kept = []
|
|
226
|
+
anchor.marks = []
|
|
227
|
+
if (!anchor.hadMarks)
|
|
228
|
+
return
|
|
229
|
+
if (anchor.lone === true && kept.length === 1) {
|
|
230
|
+
pane.selection.only(kept[0])
|
|
231
|
+
pane.selectionAnchor = pane.cursorIndex
|
|
232
|
+
pane.selectionVersion += 1
|
|
233
|
+
return
|
|
234
|
+
}
|
|
235
|
+
for (var i = 0; i < kept.length; i++)
|
|
236
|
+
pane.selection.toggle(kept[i])
|
|
237
|
+
pane.selectionAnchor = pane.cursorIndex
|
|
238
|
+
if (kept.length > 0)
|
|
239
|
+
pane.selectionVersion += 1
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// Runs on each rows reply while an anchor stands. A miss in the listing's first window is not yet a miss when the anchor asked for its own window above. A gone name falls back to the clamped old index, which keeps the view where the user left it.
|
|
243
|
+
function apply(pane, anchor, rowH) {
|
|
244
|
+
if (!anchor) {
|
|
245
|
+
return null
|
|
246
|
+
}
|
|
247
|
+
if (pane.path !== anchor.path) {
|
|
248
|
+
return null
|
|
249
|
+
}
|
|
250
|
+
if (anchor.needPaths)
|
|
251
|
+
return anchor
|
|
252
|
+
sweepMarks(pane, anchor)
|
|
253
|
+
// F3: past the first window the cursor name alone must not finish held marks past it.
|
|
254
|
+
if (anchor.start > 0 && pane.held !== anchor.start && pane.total > anchor.start)
|
|
255
|
+
return anchor
|
|
256
|
+
var at = indexOf(pane, anchor.name)
|
|
257
|
+
// F2: marks past the held window resolve through one batched locate after.
|
|
258
|
+
if (anchor.marks && anchor.marks.length > 0 && !anchor.locateDone && pane.backend && pane.backend.send) {
|
|
259
|
+
var paths = []
|
|
260
|
+
for (var m = 0; m < anchor.marks.length; m++) {
|
|
261
|
+
if (anchor.marks[m].name !== null)
|
|
262
|
+
paths.push(pane.join(pane.path, anchor.marks[m].name))
|
|
263
|
+
}
|
|
264
|
+
if (paths.length > 0 && !anchor.locateSent) {
|
|
265
|
+
anchor.locateId = ++locateSeq
|
|
266
|
+
pane.backend.send({ c: "locate", paths: paths, id: anchor.locateId })
|
|
267
|
+
anchor.locateSent = true
|
|
268
|
+
anchor.locatePaths = paths
|
|
269
|
+
// A gone cursor file still lands on the clamped old index while its marks resolve.
|
|
270
|
+
if (pane.total > 0) {
|
|
271
|
+
landOn(pane, at >= 0 ? at : Math.min(anchor.index, pane.total - 1), anchor)
|
|
272
|
+
Hold.restoreView(pane, anchor, rowH)
|
|
273
|
+
}
|
|
274
|
+
return anchor
|
|
275
|
+
}
|
|
276
|
+
if (anchor.locateSent && !anchor.locateDone)
|
|
277
|
+
return anchor
|
|
278
|
+
}
|
|
279
|
+
if (at >= 0) {
|
|
280
|
+
landOn(pane, at, anchor)
|
|
281
|
+
Hold.restoreView(pane, anchor, rowH)
|
|
282
|
+
finishMarks(pane, anchor)
|
|
283
|
+
return null
|
|
284
|
+
}
|
|
285
|
+
// Still the first window rather than the one asked for above, so keep waiting, but only while that window can still exist: a listing that shrank past the offset comes back clamped to row 0 instead.
|
|
286
|
+
if (anchor.start > 0 && pane.held === 0 && pane.total > anchor.start) {
|
|
287
|
+
return anchor
|
|
288
|
+
}
|
|
289
|
+
if (anchor.renamed && !anchor.locateSent) anchor.locateId = ++locateSeq
|
|
290
|
+
if (Hold.locateRenamed(pane, anchor)) return anchor
|
|
291
|
+
if (pane.total > 0) {
|
|
292
|
+
landOn(pane, Math.min(anchor.index, pane.total - 1), anchor)
|
|
293
|
+
Hold.restoreView(pane, anchor, rowH)
|
|
294
|
+
}
|
|
295
|
+
finishMarks(pane, anchor)
|
|
296
|
+
return null
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
// The batched locate answer for F2: indices outside the held window join kept, the gone stay gone, and the cursor lands found-or-clamped either way.
|
|
300
|
+
// Sample input: fillLocated(pane, anchor, [{ path: "/d/a", index: 3 }]) keeps 3 and lands the cursor.
|
|
301
|
+
function fillLocated(pane, anchor, matches, rowH) {
|
|
302
|
+
if (!anchor)
|
|
303
|
+
return null
|
|
304
|
+
Hold.fillLocated(pane, anchor, matches)
|
|
305
|
+
var at = indexOf(pane, anchor.name)
|
|
306
|
+
if (pane.total > 0 && !anchor.landed) {
|
|
307
|
+
landOn(pane, at >= 0 ? at : Math.min(anchor.index, pane.total - 1), anchor)
|
|
308
|
+
Hold.restoreView(pane, anchor, rowH)
|
|
309
|
+
}
|
|
310
|
+
finishMarks(pane, anchor)
|
|
311
|
+
return null
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
// A watch's anchor moves the cursor and re-marks the same files: the operator's marks belong to them, and a change another program made must not move those marks onto other files. A delete's anchor selects instead, because its rows no longer exist and a bare cursor would restart the keyboard.
|
|
315
|
+
function landOn(pane, index, anchor) {
|
|
316
|
+
if (anchor.select)
|
|
317
|
+
pane.selectOnly(index, 0)
|
|
318
|
+
else
|
|
319
|
+
pane.setCursor(index, 0)
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
// A preference re-list keeps selection and cursor by name; a mark outside the held window has no name to keep, so a partial selection clears whole rather than keeping its subset.
|
|
323
|
+
function preference(pane) {
|
|
324
|
+
var hasRowFor = !!pane.rowFor
|
|
325
|
+
var cursorRow = hasRowFor ? pane.rowFor(pane.cursorIndex) : null
|
|
326
|
+
var names = []
|
|
327
|
+
var indices = pane.selectedIndices ? pane.selectedIndices() : []
|
|
328
|
+
if (hasRowFor) {
|
|
329
|
+
for (var i = 0; i < indices.length; i++) {
|
|
330
|
+
var row = pane.rowFor(indices[i])
|
|
331
|
+
if (!row) { names = []; break }
|
|
332
|
+
names.push(String(row.n))
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
return { name: cursorRow ? String(cursorRow.n) : "", index: pane.cursorIndex,
|
|
336
|
+
start: pane.held, path: pane.path, selected: names }
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
// Resolves only on the rows reply for the asked window; a scrolled reply or a moved cursor drops the anchor instead of yanking it.
|
|
340
|
+
function applyPreference(pane, anchor) {
|
|
341
|
+
if (!anchor)
|
|
342
|
+
return null
|
|
343
|
+
if (pane.path !== anchor.path)
|
|
344
|
+
return null
|
|
345
|
+
if (pane.cursorIndex !== 0 && pane.cursorIndex !== anchor.index)
|
|
346
|
+
return null
|
|
347
|
+
if (pane.held !== anchor.start) {
|
|
348
|
+
if (anchor.start > 0 && pane.held === 0 && pane.total > anchor.start)
|
|
349
|
+
return anchor
|
|
350
|
+
if (!(anchor.start > 0 && pane.total <= anchor.start))
|
|
351
|
+
return null
|
|
352
|
+
}
|
|
353
|
+
var cursorAt = -1
|
|
354
|
+
for (var i = 0; i < pane.rows.length; i++) {
|
|
355
|
+
if (String(pane.rows[i].n) === anchor.name) {
|
|
356
|
+
cursorAt = pane.held + i
|
|
357
|
+
break
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
if (cursorAt < 0 && pane.total > 0)
|
|
361
|
+
cursorAt = Math.min(anchor.index, pane.total - 1)
|
|
362
|
+
var marks = []
|
|
363
|
+
for (var s = 0; s < anchor.selected.length; s++) {
|
|
364
|
+
for (var r = 0; r < pane.rows.length; r++) {
|
|
365
|
+
if (String(pane.rows[r].n) === anchor.selected[s]) {
|
|
366
|
+
marks.push(pane.held + r)
|
|
367
|
+
break
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
if (cursorAt >= 0)
|
|
372
|
+
pane.setCursor(cursorAt, 0)
|
|
373
|
+
pane.selection.clear()
|
|
374
|
+
for (var m = 0; m < marks.length; m++)
|
|
375
|
+
pane.selection.toggle(marks[m])
|
|
376
|
+
if (marks.length > 0 || cursorAt >= 0)
|
|
377
|
+
pane.selectionVersion++
|
|
378
|
+
return null
|
|
379
|
+
}
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
.pragma library
|
|
2
|
+
|
|
3
|
+
// The resolve and the viewport restore Anchor.js calls, split out at its hard cap.
|
|
4
|
+
|
|
5
|
+
function holdLeaf(path) {
|
|
6
|
+
var text = String(path || "")
|
|
7
|
+
var cut = text.lastIndexOf("/")
|
|
8
|
+
return cut < 0 ? text : text.substring(cut + 1)
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
// The row height the anchor measures in: a grid tile row in the grid view, a text row elsewhere.
|
|
12
|
+
function rowHeight(pane, rowH) {
|
|
13
|
+
if (pane.viewMode === "grid" && pane.listArea && typeof pane.listArea.cellHeightPx === "number")
|
|
14
|
+
return pane.listArea.cellHeightPx
|
|
15
|
+
return rowH || pane.anchorRowHeight
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// The cursor's view position as a restorable row: a tile row in the grid view, a list row elsewhere.
|
|
19
|
+
function viewRow(pane) {
|
|
20
|
+
var view = cursorView(pane)
|
|
21
|
+
if (pane.viewMode === "grid" && pane.listArea && typeof pane.listArea.columns === "number" && pane.listArea.columns > 0)
|
|
22
|
+
return Math.floor(view / pane.listArea.columns)
|
|
23
|
+
return view
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// The surface that scrolls: the active column's own list in the columns view, the pane's list area elsewhere.
|
|
27
|
+
function viewport(pane) {
|
|
28
|
+
if (pane.viewMode === "columns" && pane.columnsArea)
|
|
29
|
+
return pane.columnsArea.activeColumn().viewport
|
|
30
|
+
return pane.listArea || null
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Where the cursor row sits in the viewport, which restoreView puts back once a re-list has reset the view to its top.
|
|
34
|
+
function viewFields(pane, rowH) {
|
|
35
|
+
var area = viewport(pane)
|
|
36
|
+
var rh = rowHeight(pane, rowH)
|
|
37
|
+
var view = viewRow(pane)
|
|
38
|
+
var contentY = area && typeof area.contentY === "number" ? area.contentY : 0
|
|
39
|
+
var originY = area && typeof area.originY === "number" ? area.originY : 0
|
|
40
|
+
return { offset: view * rh + originY - contentY, rowH: rh, view: view }
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Lists the anchor's own directory; a navigation while the anchor waited drops the anchor and lists nothing, so the debt never travels.
|
|
44
|
+
function startList(pane, anchor) {
|
|
45
|
+
if (!anchor || pane.path !== anchor.path)
|
|
46
|
+
return null
|
|
47
|
+
if (pane.recentMode && pane.recentMode.length > 0) {
|
|
48
|
+
// Recent may deliver cached history synchronously, so its run must already see the anchor.
|
|
49
|
+
pane.wire.anchor = anchor
|
|
50
|
+
pane.refresh("")
|
|
51
|
+
return anchor
|
|
52
|
+
}
|
|
53
|
+
pane.openWithoutHistory(anchor.path, { keptQuery: pane.filterQuery, wantChanged: anchor.wantChanged === true, inPlace: true })
|
|
54
|
+
if (anchor.wantChanged) {
|
|
55
|
+
pane.reloadFrom = anchor.reloadFrom
|
|
56
|
+
pane.reloadChanged = -1
|
|
57
|
+
}
|
|
58
|
+
if (anchor.start > 0)
|
|
59
|
+
pane.backend.window(anchor.start, pane.windowSize)
|
|
60
|
+
return anchor
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function fillPaths(pane, anchor, list) {
|
|
64
|
+
if (!anchor || !anchor.needPaths)
|
|
65
|
+
return anchor
|
|
66
|
+
var need = anchor.needPaths
|
|
67
|
+
for (var i = 0; i < need.length && i < list.length; i++) {
|
|
68
|
+
var path = String(list[i] || "")
|
|
69
|
+
var nm = pane.recentMode && pane.recentMode.length > 0 ? path.substring(1) : holdLeaf(path)
|
|
70
|
+
for (var m = 0; m < anchor.marks.length; m++) {
|
|
71
|
+
if (anchor.marks[m].index === need[i])
|
|
72
|
+
anchor.marks[m].name = nm
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
anchor.needPaths = null
|
|
76
|
+
if (pane.pathsPending && pane.pathsPending.kind === "anchor")
|
|
77
|
+
pane.pathsPending = null
|
|
78
|
+
return startList(pane, anchor)
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function cursorView(pane) {
|
|
82
|
+
if (pane.shown === null || pane.shown === undefined)
|
|
83
|
+
return pane.cursorIndex
|
|
84
|
+
var at = pane.shown.indexOf(pane.cursorIndex)
|
|
85
|
+
return at < 0 ? 0 : at
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// A renamed row that sorts outside the held window is asked of the backend, which alone knows where it landed; the anchor waits for the reply.
|
|
89
|
+
// Sample output: {"c":"locate","paths":["/d/zzz.txt"],"id":2147483649}
|
|
90
|
+
function locateRenamed(pane, anchor) {
|
|
91
|
+
if (!anchor.renamed || anchor.locateDone || pane.total <= 0)
|
|
92
|
+
return false
|
|
93
|
+
if (anchor.locateSent)
|
|
94
|
+
return true
|
|
95
|
+
if (!pane.backend || !pane.backend.send)
|
|
96
|
+
return false
|
|
97
|
+
pane.backend.send({ c: "locate", paths: [pane.join(pane.path, anchor.name)], id: anchor.locateId })
|
|
98
|
+
anchor.locateSent = true
|
|
99
|
+
return true
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// The located renamed row takes the cursor and the selection and is revealed, with its own window asked for when the rows are not held.
|
|
103
|
+
function landRenamed(pane, anchor, found) {
|
|
104
|
+
if (found === undefined || !(found >= 0 && found < pane.total))
|
|
105
|
+
return
|
|
106
|
+
// The reveal clamps against the laid-out height, and the count that just passed through 0 has not laid out yet.
|
|
107
|
+
var area = viewport(pane)
|
|
108
|
+
if (area && typeof area.forceLayout === "function")
|
|
109
|
+
area.forceLayout()
|
|
110
|
+
pane.selectOnly(found)
|
|
111
|
+
if (!pane.rowFor(found))
|
|
112
|
+
pane.backend.window(found, pane.windowSize)
|
|
113
|
+
anchor.landed = true
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function fillLocated(pane, anchor, matches) {
|
|
117
|
+
if (!anchor)
|
|
118
|
+
return null
|
|
119
|
+
anchor.locateDone = true
|
|
120
|
+
var byPath = {}
|
|
121
|
+
for (var i = 0; i < (matches || []).length; i++) {
|
|
122
|
+
var item = matches[i]
|
|
123
|
+
byPath[String(item.path || "")] = Number(item.index)
|
|
124
|
+
}
|
|
125
|
+
if (anchor.renamed)
|
|
126
|
+
landRenamed(pane, anchor, byPath[pane.join(pane.path, anchor.name)])
|
|
127
|
+
var pending = []
|
|
128
|
+
for (var m = 0; m < (anchor.marks || []).length; m++) {
|
|
129
|
+
var mark = anchor.marks[m]
|
|
130
|
+
var key = (mark.name !== null && pane.join) ? pane.join(pane.path, mark.name) : ""
|
|
131
|
+
var at = byPath[key]
|
|
132
|
+
if (at !== undefined && at >= 0)
|
|
133
|
+
anchor.kept.push(at)
|
|
134
|
+
else if (mark.name !== null)
|
|
135
|
+
pending.push(mark)
|
|
136
|
+
}
|
|
137
|
+
anchor.marks = pending
|
|
138
|
+
return anchor
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function restoreView(pane, anchor, rowH) {
|
|
142
|
+
var area = viewport(pane)
|
|
143
|
+
if (!area || anchor.offset === undefined)
|
|
144
|
+
return
|
|
145
|
+
// The count that just passed through 0 has not laid out yet, and a clamp against its stale height pins the view to the top.
|
|
146
|
+
if (typeof area.forceLayout === "function")
|
|
147
|
+
area.forceLayout()
|
|
148
|
+
var rh = rowHeight(pane, rowH || anchor.rowH)
|
|
149
|
+
var originY = typeof area.originY === "number" ? area.originY : 0
|
|
150
|
+
var view = viewRow(pane)
|
|
151
|
+
var y = view * rh + originY - anchor.offset
|
|
152
|
+
// A restore past either end draws a blank strip, so clamp to the area's valid range when it names one.
|
|
153
|
+
if (typeof area.contentY === "number") {
|
|
154
|
+
if (typeof area.contentHeight === "number" && typeof area.height === "number") {
|
|
155
|
+
// The grid's top margin holds its resting contentY at originY less the margin, so the valid range takes both margins.
|
|
156
|
+
var lo = originY - (typeof area.topMargin === "number" ? area.topMargin : 0)
|
|
157
|
+
var hi = Math.max(lo, area.contentHeight - area.height + originY + (typeof area.bottomMargin === "number" ? area.bottomMargin : 0))
|
|
158
|
+
y = Math.max(lo, Math.min(hi, y))
|
|
159
|
+
}
|
|
160
|
+
area.contentY = y
|
|
161
|
+
}
|
|
162
|
+
}
|