diff --git a/build-system/compile/sources.js b/build-system/compile/sources.js
index 3f97cfc23426..158d83a488cd 100644
--- a/build-system/compile/sources.js
+++ b/build-system/compile/sources.js
@@ -110,6 +110,10 @@ const CLOSURE_SRC_GLOBS = [
'extensions/amp-animation/**/*.js',
// Needed for amp-carousel 0.2, amp-inline-gallery, amp-stream-gallery
'extensions/amp-base-carousel/**/*.js',
+ // Needed for carousel autolightbox
+ 'extensions/amp-lightbox-gallery/1.0/*.js',
+ // Needed for amp-lightbox-gallery using amp-lightbox
+ 'extensions/amp-lightbox/1.0/*.js',
// For amp-bind in the web worker (ww.js).
'extensions/amp-bind/**/*.js',
// Needed to access to Variant interface from other extensions
diff --git a/build-system/test-configs/dep-check-config.js b/build-system/test-configs/dep-check-config.js
index c2e4cc6accf4..22abaece03bc 100644
--- a/build-system/test-configs/dep-check-config.js
+++ b/build-system/test-configs/dep-check-config.js
@@ -246,6 +246,11 @@ exports.rules = [
'extensions/amp-stream-gallery/1.0/amp-stream-gallery.js->extensions/amp-base-carousel/1.0/base-carousel.jss.js',
'extensions/amp-stream-gallery/1.0/stream-gallery.js->extensions/amp-base-carousel/1.0/base-carousel.js',
+ // Autolightboxing dependencies
+ 'extensions/amp-base-carousel/1.0/base-carousel.js->extensions/amp-lightbox-gallery/1.0/component.js',
+ 'extensions/amp-base-carousel/1.0/scroller.js->extensions/amp-lightbox-gallery/1.0/context.js',
+ 'extensions/amp-lightbox-gallery/1.0/provider.js->extensions/amp-lightbox/1.0/component.js',
+
// Facebook components
'extensions/amp-facebook-page/0.1/amp-facebook-page.js->extensions/amp-facebook/0.1/facebook-loader.js',
'extensions/amp-facebook-comments/0.1/amp-facebook-comments.js->extensions/amp-facebook/0.1/facebook-loader.js',
diff --git a/extensions/amp-base-carousel/1.0/base-carousel.js b/extensions/amp-base-carousel/1.0/base-carousel.js
index 9749d742628c..d54caf8ec31a 100644
--- a/extensions/amp-base-carousel/1.0/base-carousel.js
+++ b/extensions/amp-base-carousel/1.0/base-carousel.js
@@ -27,6 +27,7 @@ import {CarouselContext} from './carousel-context';
import {ContainWrapper} from '../../../src/preact/component';
import {Scroller} from './scroller';
import {WithAmpContext} from '../../../src/preact/context';
+import {WithLightbox} from '../../amp-lightbox-gallery/1.0/component';
import {forwardRef} from '../../../src/preact/compat';
import {isRTL} from '../../../src/dom';
import {mod} from '../../../src/utils/math';
@@ -93,6 +94,7 @@ function BaseCarouselWithRef(
controls = Controls.AUTO,
defaultSlide = 0,
dir = Direction.AUTO,
+ lightbox = false,
loop,
mixedLength = false,
onFocus,
@@ -298,7 +300,6 @@ function BaseCarouselWithRef(
direction: rtl ? Direction.RTL : Direction.LTR,
}}
ref={containRef}
- contentRef={contentRef}
onFocus={(e) => {
if (onFocus) {
onFocus(e);
@@ -319,6 +320,12 @@ function BaseCarouselWithRef(
}}
tabIndex="0"
wrapperClassName={classes.carousel}
+ contentAs={lightbox ? WithLightbox : 'div'}
+ contentRef={contentRef}
+ contentProps={{
+ enableActivation: false,
+ render: () => children,
+ }}
{...rest}
>
{!hideControls && (
@@ -336,6 +343,7 @@ function BaseCarouselWithRef(
alignment={snapAlign}
autoAdvanceCount={autoAdvanceCount}
axis={axis}
+ lightbox={lightbox}
loop={loop}
mixedLength={mixedLength}
restingIndex={currentSlide}
diff --git a/extensions/amp-base-carousel/1.0/scroller.js b/extensions/amp-base-carousel/1.0/scroller.js
index d07ef08853c1..34fa0e3edad0 100644
--- a/extensions/amp-base-carousel/1.0/scroller.js
+++ b/extensions/amp-base-carousel/1.0/scroller.js
@@ -21,6 +21,7 @@ import {
getPercentageOffsetFromAlignment,
scrollContainerToElement,
} from './dimensions';
+import {LightboxGalleryContext} from '../../amp-lightbox-gallery/1.0/context';
import {debounce} from '../../../src/utils/rate-limit';
import {forwardRef} from '../../../src/preact/compat';
import {mod} from '../../../src/utils/math';
@@ -28,6 +29,7 @@ import {setStyle} from '../../../src/style';
import {toWin} from '../../../src/types';
import {
useCallback,
+ useContext,
useImperativeHandle,
useLayoutEffect,
useMemo,
@@ -58,6 +60,7 @@ function ScrollerWithRef(
autoAdvanceCount,
axis,
children,
+ lightbox,
loop,
mixedLength,
restingIndex,
@@ -136,6 +139,7 @@ function ScrollerWithRef(
*/
const scrollOffset = useRef(0);
+ const {open: openLightbox} = useContext(LightboxGalleryContext);
const slides = renderSlides(
{
alignment,
@@ -143,6 +147,7 @@ function ScrollerWithRef(
loop,
mixedLength,
offsetRef,
+ openLightbox: lightbox && openLightbox,
pivotIndex,
restingIndex,
snap,
@@ -348,6 +353,7 @@ function renderSlides(
mixedLength,
restingIndex,
offsetRef,
+ openLightbox,
pivotIndex,
snap,
snapBy,
@@ -357,6 +363,11 @@ function renderSlides(
classes
) {
const {length} = children;
+ const lightboxProps = openLightbox && {
+ role: 'button',
+ tabindex: '0',
+ onClick: () => openLightbox(),
+ };
const slides = children.map((child, index) => {
const key = `slide-${child.key || index}`;
return (
@@ -376,6 +387,7 @@ function renderSlides(
style={{
flex: mixedLength ? '0 0 auto' : `0 0 ${100 / visibleCount}%`,
}}
+ {...lightboxProps}
>
{child}
diff --git a/extensions/amp-lightbox-gallery/1.0/component.js b/extensions/amp-lightbox-gallery/1.0/component.js
new file mode 100644
index 000000000000..9c249a0d6acc
--- /dev/null
+++ b/extensions/amp-lightbox-gallery/1.0/component.js
@@ -0,0 +1,18 @@
+/**
+ * Copyright 2021 The AMP HTML Authors. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS-IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+export {LightboxGalleryProvider} from './provider';
+export {WithLightbox} from './consumer';
diff --git a/extensions/amp-lightbox-gallery/1.0/component.type.js b/extensions/amp-lightbox-gallery/1.0/component.type.js
new file mode 100644
index 000000000000..b420a1402f6d
--- /dev/null
+++ b/extensions/amp-lightbox-gallery/1.0/component.type.js
@@ -0,0 +1,48 @@
+/**
+ * Copyright 2021 The AMP HTML Authors. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS-IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/** @externs */
+
+/** @const */
+var LightboxGalleryDef = {};
+
+/**
+ * @typedef {{
+ * children: (PreactDef.Renderable),
+ * render: (function():PreactDef.Renderable|undefined),
+ * }}
+ */
+LightboxGalleryDef.Props;
+
+/**
+ * @typedef {{
+ * as: (string|undefined),
+ * children: (!PreactDef.Renderable),
+ * enableActivation: (boolean|undefined),
+ * onClick: (function(Event)|undefined)
+ * render: (function():PreactDef.Renderable),
+ * }}
+ */
+LightboxGalleryDef.WithLightboxProps;
+
+/**
+ * @typedef {{
+ * deregister: (function(string):undefined),
+ * register: (function(string, Element):undefined),
+ * open: (function:undefined),
+ * }}
+ */
+LightboxGalleryDef.ContextProps;
diff --git a/extensions/amp-lightbox-gallery/1.0/consumer.js b/extensions/amp-lightbox-gallery/1.0/consumer.js
new file mode 100644
index 000000000000..aff8a049715e
--- /dev/null
+++ b/extensions/amp-lightbox-gallery/1.0/consumer.js
@@ -0,0 +1,73 @@
+/**
+ * Copyright 2021 The AMP HTML Authors. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS-IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import * as Preact from '../../../src/preact';
+import {LightboxGalleryContext} from './context';
+import {sequentialIdGenerator} from '../../../src/utils/id-generator';
+import {
+ useContext,
+ useLayoutEffect,
+ useMemo,
+ useState,
+} from '../../../src/preact';
+
+const generateLightboxItemKey = sequentialIdGenerator();
+
+const DEFAULT_ARIA_LABEL = 'Open content in a lightbox view.';
+const DEFAULT_ACTIVATION_PROPS = {
+ 'aria-label': DEFAULT_ARIA_LABEL,
+ role: 'button',
+ tabIndex: '0',
+};
+
+/**
+ * @param {!LightboxGalleryDef.WithLightboxProps} props
+ * @return {PreactDef.Renderable}
+ */
+export function WithLightbox({
+ as: Comp = 'div',
+ children,
+ enableActivation = true,
+ onClick: customOnClick,
+ render = () => children,
+ ...rest
+}) {
+ const [genKey] = useState(generateLightboxItemKey);
+ const {open, register, deregister} = useContext(LightboxGalleryContext);
+ useLayoutEffect(() => {
+ register(genKey, render);
+ return () => deregister(genKey);
+ }, [genKey, deregister, register, render]);
+
+ const activationProps = useMemo(
+ () =>
+ enableActivation && {
+ ...DEFAULT_ACTIVATION_PROPS,
+ onClick: () => {
+ if (customOnClick) {
+ customOnClick();
+ }
+ open();
+ },
+ },
+ [customOnClick, enableActivation, open]
+ );
+ return (
+ abc abc