Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .lune/commands/install/init.luau
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ install.arguments = {

local PROJECT_FILE = "plugin/default.project.jsonc"
local SOURCEMAP_PATH = "plugin/generated/sourcemap.json"
local VERSION_PIN = "0.712.0.7120913"
local VERSION_PIN = "0.719.0.7191339"
local PACKAGE_DIRS = {
"Packages",
-- "DevPackages",
Expand Down
125 changes: 113 additions & 12 deletions .lune/commands/install/patches.luau
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,22 @@

Discovers git-format patch files in plugin/patches/ and applies them
to the corresponding packages in plugin/Packages/_Index/.

Application is resilient to upstream drift:
1. First try a strict `git apply` -- the cheap, atomic happy path.
2. On failure, retry with `git apply --reject` so the hunks that
still match are applied and any drifted hunks are written to
`<file>.rej` next to the source. The install then continues
with a warning so the next step (`lune run patch <pkg>` to
regenerate the patch) is obvious.

Note: `git apply --3way` would be nicer (inline conflict markers)
but it requires the patch's recorded blob hashes to be present in
the local object database. Our patches are generated in an ephemeral
`git init` repo that is deleted after `git diff --cached`, and
plugin/Packages/_Index is gitignored, so those blobs are never
available at apply time. `--reject` gives equivalent recoverability
via sidecar `.rej` files.
]]

local fs = require("@lune/fs")
Expand All @@ -13,6 +29,71 @@ local color = require("../../utils/color")
local PATCHES_DIR = "plugin/patches"
local PACKAGES_INDEX_DIR = "plugin/Packages/_Index"

--[[
Recursively collects any `*.rej` files under a directory.
]]
local function collectRejects(root: string, out: { string })
if not fs.isDir(root) then
return
end
for _, entry in fs.readDir(root) do
local entryPath = `{root}/{entry}`
if fs.isDir(entryPath) then
collectRejects(entryPath, out)
elseif entry:match("%.rej$") then
table.insert(out, entryPath)
end
end
end

--[[
Applies a single patch file to the target package directory, preferring
a clean apply but falling back to `--reject` so that drift is recoverable
rather than fatal.

Returns:
status: "clean" | "conflicts" | "failed"
message: human-readable detail (stderr from git, or a summary)
rejects: list of `.rej` files produced under targetPath, if any
]]
local function applyPatch(patchFilePath: string, targetPath: string): (string, string, { string })
local strict = process.exec("git", {
"apply",
"--directory",
targetPath,
patchFilePath,
})
if strict.ok then
return "clean", strict.stdout, {}
end

-- Lenient retry: apply hunks that still match and dump the rest into
-- sibling `*.rej` files. Git exits non-zero whenever any hunk is
-- rejected, but the partial apply succeeds.
local lenient = process.exec("git", {
"apply",
"--reject",
"--directory",
targetPath,
patchFilePath,
})

local rejects: { string } = {}
collectRejects(targetPath, rejects)

if #rejects > 0 then
return "conflicts", lenient.stderr, rejects
end

if lenient.ok then
return "clean", lenient.stdout, {}
end

-- Neither attempt produced anything useful (e.g. patch references a
-- file that does not exist). Surface the original strict-apply error.
return "failed", strict.stderr, {}
end

--[[
Applies all patches from plugin/patches/ to installed Wally packages.

Expand All @@ -21,7 +102,9 @@ local PACKAGES_INDEX_DIR = "plugin/Packages/_Index"
→ applies to plugin/Packages/_Index/jsdotlua_jest-runtime@3.10.0/

@param quiet Whether to suppress output
@return True if all patches applied successfully, false otherwise
@return True if all patches applied without unrecoverable failures.
Patches that produced conflict markers or `.rej` files are
reported as warnings but do not fail the install.
]]
local function applyPatches(quiet: boolean?): boolean
if not fs.isDir(PATCHES_DIR) then
Expand All @@ -41,6 +124,9 @@ local function applyPatches(quiet: boolean?): boolean

table.sort(patchFiles)

local conflictedPatches: { { patch: string, rejects: { string } } } = {}
local failedPatches: { { patch: string, message: string } } = {}

for _, patchFile in patchFiles do
local baseName = patchFile:gsub("%.patch$", "")
local targetDirName = baseName
Expand All @@ -65,23 +151,38 @@ local function applyPatches(quiet: boolean?): boolean
end

local patchFilePath = `{PATCHES_DIR}/{patchFile}`
local result = process.exec("git", {
"apply",
"--directory",
targetPath,
patchFilePath,
})

if not result.ok then
local status, message, rejects = applyPatch(patchFilePath, targetPath)

if status == "clean" then
continue
elseif status == "conflicts" then
table.insert(conflictedPatches, { patch = patchFile, rejects = rejects })
if not quiet then
print(color.yellow(` Conflict: {patchFile} applied with drift`))
for _, rej in rejects do
print(color.yellow(` rejected hunks: {rej}`))
end
end
else
table.insert(failedPatches, { patch = patchFile, message = message })
if not quiet then
print(color.red(` Failed to apply patch: {patchFile}`))
print(color.red(` {result.stderr}`))
print(color.red(` {message}`))
end
return false
end
end

return true
if #conflictedPatches > 0 and not quiet then
print()
print(color.bold(color.yellow("Some patches drifted from upstream and need attention:")))
for _, entry in conflictedPatches do
print(color.yellow(` - {entry.patch}`))
end
print(color.dim(" Resolve the conflicts (or apply the .rej hunks by hand), then run"))
print(color.dim(" `lune run patch plugin/Packages/_Index/<pkg>` twice to regenerate."))
end

return #failedPatches == 0
end

return applyPatches
162 changes: 113 additions & 49 deletions plugin/patches/Foundation.patch
Original file line number Diff line number Diff line change
@@ -1,89 +1,116 @@
diff --git a/Foundation/Components/Dialog/Dialog.lua b/Foundation/Components/Dialog/Dialog.lua
index 7844af0..ca25f6c 100644
--- a/Foundation/Components/Dialog/Dialog.lua
+++ b/Foundation/Components/Dialog/Dialog.lua
@@ -297,6 +297,7 @@ local function Dialog(dialogProps: DialogInternalProps)
end)
else variants.backdrop.backgroundStyle,
ZIndex = 2,
+ InputSink = Enum.InputSink.All,
testId = `{props.testId}--backdrop`,
})
else nil,
diff --git a/Foundation/Components/Popover/Content/PopoverContent.lua b/Foundation/Components/Popover/Content/PopoverContent.lua
index 709467c..c91e3fe 100644
--- a/Foundation/Components/Popover/Content/PopoverContent.lua
+++ b/Foundation/Components/Popover/Content/PopoverContent.lua
@@ -205,6 +205,7 @@ local function PopoverContent(contentProps: PopoverContentProps, forwardedRef: R
Position = position:map(function(value: Vector2)
return UDim2.fromOffset(value.X, value.Y)
end),
+ InputSink = Enum.InputSink.All,
selection = props.selection,
selectionGroup = props.selectionGroup,
sizeConstraint = {
diff --git a/Foundation/Components/Sheet/BottomSheet.lua b/Foundation/Components/Sheet/BottomSheet.lua
index ad7336b..6e8ff45 100644
index b70c89d..f3fd8fa 100644
--- a/Foundation/Components/Sheet/BottomSheet.lua
+++ b/Foundation/Components/Sheet/BottomSheet.lua
@@ -462,6 +462,7 @@ local function BottomSheet(sheetProps: SheetProps, ref: React.Ref<Instance>)
@@ -507,6 +507,7 @@ local function BottomSheet(sheetProps: SheetProps, ref: React.Ref<Instance>)
ClipsDescendants = false,
BackgroundTransparency = 1,
ScrollingDirection = Enum.ScrollingDirection.Y,
+ ScrollingEnabled = false,
ScrollBarThickness = 0,
ref = outerScrollingRef,
[React.Change.CanvasPosition] = function(instance: ScrollingFrame)
@@ -498,42 +499,8 @@ local function BottomSheet(sheetProps: SheetProps, ref: React.Ref<Instance>)
@@ -543,20 +544,8 @@ local function BottomSheet(sheetProps: SheetProps, ref: React.Ref<Instance>)
-- Needed to sink the onActivated event to the backdrop
onActivated = Dash.noop,
testId = props.testId,
- tag = "bg-surface-100 radius-large col items-center clip padding-top-small",
+ tag = "bg-surface-100 radius-large col items-center clip",
- tag = if Flags.FoundationSheetFullBleed
- then {
- ["col items-center radius-large clip bg-surface-100"] = true,
- ["padding-top-medium"] = not hasFullBleed,
- }
- else "col items-center padding-top-small radius-large clip bg-surface-100",
+ tag = "col items-center radius-large clip bg-surface-100",
}, {
- Gripper = React.createElement(View, {
- ZIndex = 3,
- backgroundStyle = tokens.Color.Content.Muted,
- tag = "padding-y-small size-1000-100 radius-small align-y-center",
- testId = `{props.testId}--gripper`,
- }, {
- TouchTarget = React.createElement(View, {
- tag = "size-1000-600",
- stateLayer = {
- affordance = StateLayerAffordance.None,
- },
- onActivated = function()
- -- Cancel input ended if the gripper is pressed
- inputActive.current = false
- if #snapPoints > 1 then
- local nextIndex = currentSnapIndex.current % #snapPoints + 1
- if Flags.FoundationSheetReducedMotion and reducedMotion then
- jumpToSnapIndex(nextIndex)
- else
- springToSnapIndex(nextIndex)
- end
- if Flags.FoundationSheetFixClosingSwipe then
- local isAtMaxSnapPoint = snapValueToPixels(snapPoints[nextIndex])
- == maxSheetHeight
- if isAtMaxSnapPoint then
- setInnerScrollingEnabled(true)
- end
- end
- else
- closeSheet()
- end
- end,
- }),
- }),
- GripperContainer = if Flags.FoundationSheetFullBleed
- -- Wrap the gripper in a Folder so it stays out of the sheet's vertical layout
- -- and can overlay without reserving extra space above content.
- then React.createElement("Folder", nil, {
- Gripper = gripperElement,
- })
- else gripperElement,
Content = React.createElement(SheetContext.Provider, {
value = contextValue,
}, React.createElement(OwnerScope, { owner = elevation }, props.children)),
@@ -579,6 +568,7 @@ local function BottomSheet(sheetProps: SheetProps, ref: React.Ref<Instance>)
Size = UDim2.fromScale(1, 2),
Position = UDim2.fromScale(0, -0.5),
ZIndex = 1,
+ InputSink = Enum.InputSink.All,
stateLayer = {
affordance = StateLayerAffordance.None,
},
diff --git a/Foundation/Components/Sheet/CenterSheet.lua b/Foundation/Components/Sheet/CenterSheet.lua
index 559fb7f..4a377b5 100644
--- a/Foundation/Components/Sheet/CenterSheet.lua
+++ b/Foundation/Components/Sheet/CenterSheet.lua
@@ -337,6 +337,7 @@ local function CenterSheet(centerSheetProps: CenterSheetProps, ref: React.Ref<Gu
Size = UDim2.fromScale(2, 2),
Position = UDim2.fromScale(-0.5, -0.5),
ZIndex = 1,
+ InputSink = Enum.InputSink.All,
stateLayer = {
affordance = StateLayerAffordance.None,
},
diff --git a/Foundation/Components/Sheet/Sheet.lua b/Foundation/Components/Sheet/Sheet.lua
index f5bd39e..f0ebe64 100644
index 1546095..95d05c4 100644
--- a/Foundation/Components/Sheet/Sheet.lua
+++ b/Foundation/Components/Sheet/Sheet.lua
@@ -30,7 +30,23 @@ local function Sheet(sheetProps: SheetProps, ref: React.Ref<GuiObject>): React.R
@@ -27,11 +27,27 @@ local function Sheet(sheetProps: SheetProps, ref: React.Ref<GuiObject>): React.R
local props = withDefaults(sheetProps, defaultProps)
local isOpen, setIsOpen = React.useState(if props.isOpen == nil then true else props.isOpen)
local sheetRef = React.useRef(nil :: SheetRef?)
- local screen = if Flags.FoundationOverlayKeyboardAwarenessHardened then useScreen() else useOverlay()
+ local screen = useOverlay()

local isLandscape, setIsLandscape =
React.useState(if overlay then overlay.AbsoluteSize.X > overlay.AbsoluteSize.Y else true)
React.useState(if screen then screen.AbsoluteSize.X > screen.AbsoluteSize.Y else true)
- local displaySize, setDisplaySize = React.useState(GuiService.ViewportDisplaySize)
+
+ local isSmallDisplay, setSmallDisplay = React.useState(false)
+ React.useEffect(function()
+ if overlay == nil then
+ if screen == nil then
+ return
+ end
+
+ local function update()
+ setSmallDisplay(overlay.AbsoluteSize.X <= 500)
+ setSmallDisplay(screen.AbsoluteSize.X <= 500)
+ end
+ update()
+
+ local connection = GuiService:GetPropertyChangedSignal("ViewportDisplaySize"):Connect(update)
+ return function()
+ connection:Disconnect()
+ end
+ end, { overlay })
+ end, { screen })

React.useImperativeHandle(props.sheetRef, function()
return sheetRef.current
@@ -50,10 +66,6 @@ local function Sheet(sheetProps: SheetProps, ref: React.Ref<GuiObject>): React.R
@@ -51,10 +67,6 @@ local function Sheet(sheetProps: SheetProps, ref: React.Ref<GuiObject>): React.R
end
end, { isOpen })

Expand All @@ -93,8 +120,8 @@ index f5bd39e..f0ebe64 100644
-
React.useLayoutEffect(function()
local connection
if overlay then
@@ -78,9 +90,9 @@ local function Sheet(sheetProps: SheetProps, ref: React.Ref<GuiObject>): React.R
if screen then
@@ -79,9 +91,9 @@ local function Sheet(sheetProps: SheetProps, ref: React.Ref<GuiObject>): React.R
childProps.isOpen = nil
childProps.ref = ref
childProps.sheetRef = sheetRef
Expand All @@ -107,7 +134,7 @@ index f5bd39e..f0ebe64 100644
childProps.onClose = function()
if props.onClose then
props.onClose()
@@ -88,7 +100,7 @@ local function Sheet(sheetProps: SheetProps, ref: React.Ref<GuiObject>): React.R
@@ -89,7 +101,7 @@ local function Sheet(sheetProps: SheetProps, ref: React.Ref<GuiObject>): React.R
setIsOpen(false)
end

Expand All @@ -116,3 +143,40 @@ index f5bd39e..f0ebe64 100644
return React.createElement(BottomSheet, childProps)
elseif props.preferCenterSheet then
return React.createElement(CenterSheet, childProps)
diff --git a/Foundation/Components/Sheet/SideSheet.lua b/Foundation/Components/Sheet/SideSheet.lua
index 79ccd82..98b9ea0 100644
--- a/Foundation/Components/Sheet/SideSheet.lua
+++ b/Foundation/Components/Sheet/SideSheet.lua
@@ -288,6 +288,7 @@ local function SideSheet(sideSheetProps: SideSheetProps, ref: React.Ref<GuiObjec
Size = UDim2.fromScale(2, 2),
Position = UDim2.fromScale(-0.5, -0.5),
ZIndex = 1,
+ InputSink = Enum.InputSink.All,
stateLayer = {
affordance = StateLayerAffordance.None,
},
diff --git a/Foundation/Utility/withGuiObjectProps.lua b/Foundation/Utility/withGuiObjectProps.lua
index 1f3f3a3..11e8553 100644
--- a/Foundation/Utility/withGuiObjectProps.lua
+++ b/Foundation/Utility/withGuiObjectProps.lua
@@ -17,9 +17,6 @@ type CommonProps = Types.CommonProps
type NativeCommonProps = Types.NativeCommonProps
type SelectionGroup = Types.SelectionGroup

-local hasInputSink = Flags.FoundationGuiObjectInputSinkProperty
- and (pcall(game.GetEngineFeature, game, "GuiObjectInputSink"))
-
type ReactRefGuiObject = React.Ref<GuiObject>

-- Since GuiObjectProps is in a different form than what's actually applied, we need to manually define the applied props
@@ -63,9 +60,7 @@ local function withGuiObjectProps<T>(props: GuiObjectProps & CommonProps, basePr
baseProps.BorderColor3 = props.BorderColor3
baseProps.BorderMode = props.BorderMode
baseProps.ClipsDescendants = props.ClipsDescendants
- if hasInputSink then
- baseProps.InputSink = props.InputSink
- end
+ baseProps.InputSink = props.InputSink
baseProps.Rotation = props.Rotation
baseProps.SizeConstraint = props.SizeConstraint

Loading
Loading