feat: ship creator journey v2 and profile updates
This commit is contained in:
@@ -1,39 +1,18 @@
|
||||
import React, { useEffect, useRef } from 'react'
|
||||
import React, { useEffect, useRef, useCallback } from 'react'
|
||||
|
||||
let emojiMartRegistrationPromise = null
|
||||
let emojiMartPromise = null
|
||||
|
||||
function ensureEmojiMartRegistered() {
|
||||
if (!emojiMartRegistrationPromise) {
|
||||
emojiMartRegistrationPromise = import('emoji-mart')
|
||||
function ensureEmojiMart() {
|
||||
if (!emojiMartPromise) {
|
||||
emojiMartPromise = import('emoji-mart')
|
||||
}
|
||||
|
||||
return emojiMartRegistrationPromise
|
||||
}
|
||||
|
||||
function applyPickerProps(element, props) {
|
||||
if (!element) {
|
||||
return
|
||||
}
|
||||
|
||||
element.data = props.data
|
||||
element.onEmojiSelect = props.onEmojiSelect
|
||||
element.theme = props.theme
|
||||
element.previewPosition = props.previewPosition
|
||||
element.skinTonePosition = props.skinTonePosition
|
||||
element.maxFrequentRows = props.maxFrequentRows
|
||||
element.perLine = props.perLine
|
||||
element.navPosition = props.navPosition
|
||||
element.set = props.set
|
||||
element.locale = props.locale
|
||||
element.autoFocus = props.autoFocus
|
||||
element.searchPosition = props.searchPosition
|
||||
element.dynamicWidth = props.dynamicWidth
|
||||
element.noCountryFlags = props.noCountryFlags
|
||||
return emojiMartPromise
|
||||
}
|
||||
|
||||
export default function EmojiMartPicker({
|
||||
data,
|
||||
onEmojiSelect,
|
||||
onClickOutside,
|
||||
theme = 'auto',
|
||||
previewPosition = 'bottom',
|
||||
skinTonePosition = 'preview',
|
||||
@@ -51,56 +30,66 @@ export default function EmojiMartPicker({
|
||||
const hostRef = useRef(null)
|
||||
const pickerRef = useRef(null)
|
||||
|
||||
// Keep refs pointing at the latest callback props so stable wrappers
|
||||
// never capture a stale closure.
|
||||
const onEmojiSelectRef = useRef(onEmojiSelect)
|
||||
const onClickOutsideRef = useRef(onClickOutside)
|
||||
onEmojiSelectRef.current = onEmojiSelect
|
||||
onClickOutsideRef.current = onClickOutside
|
||||
|
||||
// Stable wrappers with fixed identity — safe to pass once to the Picker
|
||||
// constructor without needing to re-initialise the element on every render.
|
||||
const stableOnEmojiSelect = useCallback((emoji) => {
|
||||
onEmojiSelectRef.current?.(emoji)
|
||||
}, [])
|
||||
|
||||
const stableOnClickOutside = useCallback((e) => {
|
||||
onClickOutsideRef.current?.(e)
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
let cancelled = false
|
||||
|
||||
ensureEmojiMartRegistered().then(() => {
|
||||
if (cancelled || !hostRef.current) {
|
||||
return
|
||||
}
|
||||
// emoji-mart's Picker stores callbacks in `this.props` during construction.
|
||||
// connectedCallback reads from `this.props`, NOT from plain element
|
||||
// properties set after construction, so we MUST use `new Picker(props)`
|
||||
// rather than `document.createElement('em-emoji-picker')` + property
|
||||
// assignment, which would leave onEmojiSelect as null internally.
|
||||
ensureEmojiMart().then(({ Picker }) => {
|
||||
if (cancelled || !hostRef.current) return
|
||||
|
||||
if (!pickerRef.current) {
|
||||
pickerRef.current = document.createElement('em-emoji-picker')
|
||||
hostRef.current.replaceChildren(pickerRef.current)
|
||||
}
|
||||
const pickerProps = {
|
||||
data,
|
||||
onEmojiSelect: stableOnEmojiSelect,
|
||||
onClickOutside: stableOnClickOutside,
|
||||
theme,
|
||||
previewPosition,
|
||||
skinTonePosition,
|
||||
maxFrequentRows,
|
||||
perLine,
|
||||
navPosition,
|
||||
set,
|
||||
locale,
|
||||
autoFocus,
|
||||
}
|
||||
if (searchPosition !== undefined) pickerProps.searchPosition = searchPosition
|
||||
if (dynamicWidth !== undefined) pickerProps.dynamicWidth = dynamicWidth
|
||||
if (noCountryFlags !== undefined) pickerProps.noCountryFlags = noCountryFlags
|
||||
|
||||
applyPickerProps(pickerRef.current, {
|
||||
data,
|
||||
onEmojiSelect,
|
||||
theme,
|
||||
previewPosition,
|
||||
skinTonePosition,
|
||||
maxFrequentRows,
|
||||
perLine,
|
||||
navPosition,
|
||||
set,
|
||||
locale,
|
||||
autoFocus,
|
||||
searchPosition,
|
||||
dynamicWidth,
|
||||
noCountryFlags,
|
||||
})
|
||||
const el = new Picker(pickerProps)
|
||||
pickerRef.current = el
|
||||
hostRef.current.replaceChildren(el)
|
||||
}
|
||||
})
|
||||
|
||||
return () => {
|
||||
cancelled = true
|
||||
}
|
||||
}, [
|
||||
data,
|
||||
onEmojiSelect,
|
||||
theme,
|
||||
previewPosition,
|
||||
skinTonePosition,
|
||||
maxFrequentRows,
|
||||
perLine,
|
||||
navPosition,
|
||||
set,
|
||||
locale,
|
||||
autoFocus,
|
||||
searchPosition,
|
||||
dynamicWidth,
|
||||
noCountryFlags,
|
||||
])
|
||||
// Run once on mount. Callbacks stay fresh via refs; static display options
|
||||
// (theme, perLine, etc.) don't change during a single picker session.
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
|
||||
19
resources/js/components/common/extractNativeEmoji.js
Normal file
19
resources/js/components/common/extractNativeEmoji.js
Normal file
@@ -0,0 +1,19 @@
|
||||
export default function extractNativeEmoji(selection) {
|
||||
if (typeof selection === 'string') {
|
||||
return selection
|
||||
}
|
||||
|
||||
const detail = selection?.detail ?? null
|
||||
|
||||
return (
|
||||
selection?.native
|
||||
?? selection?.emoji
|
||||
?? selection?.unicode
|
||||
?? selection?.skins?.[0]?.native
|
||||
?? detail?.native
|
||||
?? detail?.emoji
|
||||
?? detail?.unicode
|
||||
?? detail?.skins?.[0]?.native
|
||||
?? ''
|
||||
)
|
||||
}
|
||||
11
resources/js/components/common/isEventWithinNode.js
Normal file
11
resources/js/components/common/isEventWithinNode.js
Normal file
@@ -0,0 +1,11 @@
|
||||
export default function isEventWithinNode(event, node) {
|
||||
if (!event || !node) {
|
||||
return false
|
||||
}
|
||||
|
||||
if (typeof event.composedPath === 'function') {
|
||||
return event.composedPath().includes(node)
|
||||
}
|
||||
|
||||
return node.contains(event.target)
|
||||
}
|
||||
Reference in New Issue
Block a user